5

I am trying to calculate the angle for an arrow on a ball, based on the position where it is going to. The arrow moves, but in a total unexplainable direction, can anybody give some pointers?

Codepen available: Codepen

I added the full code on here (EDITED based on input): I added a step to make the difference bigger for the angle calculation, not sure if that is the right way to go, but it seems a bit more functional. Plus added the +/- 90 in the angle method, but that doesnt seem to fix it. It is still feeling odd.


class Throwable {

  constructor(){
    this.throwObject = null;
    this.canDrag = null;
    this.initialDiffX = 0;
    this.initialDiffY = 0;
    this.previousX = 0;
    this.previousY = 0;
    this.intervalCounter = 0;
  }

  set x(input) {
    this.throwObject.style.left = input + 'px';
  }

  set y(input) {
    this.throwObject.style.top = input + 'px';
  }

  set rotation(input) {
    this.throwObject.style.transform = `rotate(${input}deg)`;
  }

  init(){
    this.throwObject = document.querySelector('.throwable');
    this.throwObject.addEventListener('mousedown', this.activateDrag.bind(this));
    this.throwObject.addEventListener('mouseup', this.deactivateDrag.bind(this));
    document.addEventListener('mousemove', this.drag.bind(this));
  }

  activateDrag(event) {
    this.canDrag = true;
    this.initialDiffX = event.clientX - this.throwObject.offsetLeft;
    this.initialDiffY = event.clientY - this.throwObject.offsetTop;
  }

  deactivateDrag() {
    this.canDrag = false;
  }

  drag(event) {
    if(this.canDrag === true) {
      if(this.intervalCounter >= 30) {
         this.intervalCounter = 0;
      }
      if(this.intervalCounter === 0) {
        this.previousX = event.clientX;
        this.previousY = event.clientY;
      }
      this.intervalCounter++;
      this.y = event.clientY- this.initialDiffY;
      this.x = event.clientX - this.initialDiffX;
      this.rotation = this.angle(event.clientX, event.clientY, this.previousX, this.previousY);
    }
  }

  angle(ex, ey, cx, cy) {
    var dy = ey - cy;
    var dx = ex - cx;
    return Math.atan2(dy, dx) * 180 / Math.PI + 90;
  }

  // Untility
  log(logObject) {
    let logStr = '';
    for(let key in logObject) {
      logStr += `${key}: ${logObject[key]}<br>`;
    }
    document.getElementById('log').innerHTML = logStr;
  }
}

let throwable = new Throwable();
throwable.init();

I made a mistake in comparing two different values, I fixed that, it is working way better, still have some odd behavior sometimes, seems like it doesnt know where to go in some points. But working better than before.

Barry
  • 75
  • 5
  • The ```angle()``` function is returning a value in degree, are you sure that this is what ```this.rotation``` is expecting? – Moutah Feb 10 '20 at 13:20
  • Yes its expecting degrees: ` set rotation(input) { this.throwObject.style.transform = `rotate(${input}deg)`; } ` – Barry Feb 10 '20 at 13:29

5 Answers5

3

Maybe you have some mistakes in your angle function. This works for me:

angle(cx, cy, ex, ey) {
    var dy = ey - cy ;
    var dx = cx - ex ;
    return Math.atan2(dx, dy) * 180 / Math.PI;
}
Sapikelio
  • 2,594
  • 2
  • 16
  • 40
  • 2
    Math.atan2() expects the first parameter to be the y position. – obscure Feb 10 '20 at 13:23
  • True, the first argument passed must be the Y, followed by the X. I think the mistake must be somewhere else, if you can take a look at the codepen, you can see the weird rotation behavior. – Barry Feb 10 '20 at 13:28
1

This happens because there's a difference how angles are measured between Math.atan2() and the CSS rotate transformation.

For us humans it's natural that the 12 o' clock position on an analog clock refers to the angle 0 - same for CSS rotate.

Math.atan2() however measures the angle starting from the horizontal x axis. So depending on your input coordinates it would be the 3 or 9 o' clock position.

There's an easy fix however. After calculating the angle

Math.atan2(dy, dx) * 180 / Math.PI

just subtract 90 degrees like

Math.atan2(dy, dx) * 180 / Math.PI - 90
obscure
  • 11,916
  • 2
  • 17
  • 36
  • I added it, but instead is subtracting, I added the 90, like Herman suggested, otherwise it would behave counter-clockwise. – Barry Feb 10 '20 at 14:50
  • Yes, this happens because angles range from -180 to 180 and not 0 to 360 (and the order of your input vertices is important too). – obscure Feb 10 '20 at 14:56
0

When you call this.angle() you give it twice this.throwObject.offset..., once directly and once via px and py:

let px = this.throwObject.offsetLeft;
let py = this.throwObject.offsetTop;

this.rotation = this.angle(this.throwObject.offsetLeft, this.throwObject.offsetTop, px, py)

That will result in dx and dy to be 0 in angle() making the result of Math.atan2() unpredictable.

I'm not sure about the rest of your code, but maybe you meant to call angle() like this:

this.rotation = this.angle(this.x, this.y, px, py);
Moutah
  • 332
  • 3
  • 9
  • I am getting the X-Y and store that in px-py, then I set the new values, so basically the previous are stored in px-py. – Barry Feb 10 '20 at 13:35
0

There are a couple small issues that I can see.

First, the angle method is calculating radians in range of -180 to 180 and you want it to be 0 to 360. So after angle calculation you'll want to convert something like this:

angle(ex, ey, cx, cy) {
    var dy = ey - cy;
    var dx = ex - cx;
    var theta = Math.atan2(dy, dx) * 180 / Math.PI;
    if (theta < 0) theta += 360; // convert to [0, 360]
    return theta;
}

Second, the starting angle of your element at 0 degrees is not the actual 0 degrees calculated by this method due to how js coordinates work. A quick fix is to add 90 degrees to make it match:

set rotation(input) {
    this.throwObject.style.transform = `rotate(${input + 90}deg)`;
}

It's still a little janky after these conversion but I think it's a start on the right calculations. My guess is part of the issue is having such close points for calculation.

Herman
  • 51
  • 1
  • 6
  • Thank you Herman, I tried a few solutions, also yours, but its for some reason still not working properly, I added a step interval to make the distance a little bigger for comparisons. – Barry Feb 10 '20 at 14:37
  • Can someone explain the +90 offset? When calculating the position based on sin/cos, then reconverting to the angle using tan2 my angle is off by -90 degrees. – Joshua Taylor Eppinette May 28 '22 at 20:45
  • After flipping the `ex - cx` arguments when calculating `dx`, I no longer needed the -90 degree offset. – Joshua Taylor Eppinette Jun 07 '22 at 00:06
0

What happens when intervalCounter become 0? The previus point moved to the event point, so dy, dx becomes 0 and you have a jitter: -180 + 90, +180 + 90, 0 + 90 as defined in Math.atan2. After that, the previus point is fixed until intervalCounter < 30 and you have some inceasing distance between the previus and event points, so the angle is close to the expected one.

Anyway, this is a bad coordinate filter. You can improve it by implementing simple exponential filtering or by using fixed size (30 in your case) queue for event point.

mr NAE
  • 3,144
  • 1
  • 15
  • 35