Calculate direction change

edited March 2016 in How To...

I want to calculate values based on the change in direction. If it keeps going the same it should be 1. If it goes back (so 180 degrees change) then it should be 0. And then everything in-between.

As last, the calculations should be fast! I tried some things with the cross and dot product but not sure if that is the right way to go.

Hope someone can help.

Screen Shot 2015-05-27 at 4.45.11 PM

Here some code to get started, with my attempts removed.

PVector a, b, c;

void setup() {
  size(600, 600);

  a = new PVector(50, 50);
  b = new PVector(width/2, height/2);
  c = new PVector();

}

void draw() {
  c.set(mouseX, mouseY);

  background(0);
  beginShape();
  stroke(255);
  noFill();
  vertex(a.x, a.y);
  vertex(b.x, b.y);
  vertex(c.x, c.y);
  endShape();

  // magic...


}

Answers

  • The PVector class has a heading() function that would come in handy.

    Maybe try creating two "heading vectors" made up of the difference between your connected vectors. Then just take the difference of those vector's heading() values and divide by the max.

  • code for heading in case interested.

    public float heading() {
        float angle = (float) Math.atan2(-y, x);
        return -1*angle;
      }
    

    I got it, when trying to play with the heading I discovered I did something wrong before.

    PVector a, b, c;
    
    void setup() {
      size(600, 600);
    }
    
    void draw() {
    
      a = new PVector(50, 75);
      b = new PVector(width/2, height/2);
      c = new PVector();
      c.set(mouseX, mouseY);
    
      background(0);
      beginShape();
      stroke(255);
      noFill();
      vertex(a.x, a.y);
      vertex(b.x, b.y);
      vertex(c.x, c.y);
      endShape();
    
      // magic...
    
      b.sub(a);
      c.sub(b);
    
      b.normalize();
      c.normalize();
    
      float dot = b.dot(c);
    
      dot = (dot+1) /2.0;
    
      println(dot);
    
    }
    

    Here code for dot product to show it's cheap:

      public float dot(PVector v) {
        return x*v.x + y*v.y + z*v.z;
      }
    
  • public float heading() {
        float angle = (float) Math.atan2(-y, x);
        return -1*angle;
    }
    

    Wouldn't returning -angle be more direct than multiplying by -1? :-?
    Even better, a more succinct 1-line return: :ar!

    public float heading() {
        return -atan2(-y, x);
    }
    
  • FYI the dot product of 2 vectors gives the cosine of the angle between. Which is -1 for 180° and +1 for 0°. So adding 1 and dividing by 2 gives the range you want.

  • edited May 2015

    @GoToLoop the code for the heading I posted is straight from Processing PVector.

    @quark I figured about the adding 1 and dividing by 2 as I had in my last post. However, the values are wrong.

    First, I figured that I wanted the values the other way around. So if a line keeps going in the same direction it should be 0. If the line goes back it should be 1.

    What I have now looks ok if the angle is 0, 90, 180, 270 or 360. But with a 45 angle the value makes no sense. I only have no clue why. I found it really hard to think in vector math.

    
        PVector a, b, c;
         
        void setup() {
          size(600, 600);
        }
         
        void draw() {
          
          a = new PVector(50, height/2);
          b = new PVector(width/2, height/2);
          c = new PVector();
          c.set(mouseX, mouseY);
         
          background(0);
          beginShape();
          stroke(255);
          noFill();
          vertex(a.x, a.y);
          vertex(b.x, b.y);
          vertex(c.x, c.y);  
          endShape();
         
          a.sub(b);
          b.sub(c);
           
          a.normalize();
          b.normalize();
          //c.normalize();
         
          float dot = b.dot(a);
         
          dot = (dot+1) /2.0;
         
          println(dot);
          
        }
        
     
  • Can someone please explain why a 45 angle isn't in steps of 0.25?

  • edited May 2015

    Processing uses the standard of measuring angles in units of radians. There are 2*PI radians in a circle, so 180 degrees is PI radians, 90 degrees is PI/2 radians, and 45 degrees is PI/4 radians.

    Processing provides the useful constants of TWO_PI, PI, HALF_PI, and QUARTER_PI.

  • edited May 2015

    thx, but this is about the dot product, not radians. And If i look straight left, up, right then the values are 0.0, 0.5 and 1.0 like expected, but in between it makes no sense.

  • edited May 2015

    i'm sure we've seen this question before which makes me think this is an assignment.

    Can someone please explain why a 45 angle isn't in steps of 0.25?

    a.b is defined as |a| * |b| * cos(angle). i think it's the cos that's causing the non-linear values. maybe throw an acos and a map in there...

    float dot = b.dot(a);
    dot = map(acos(dot), 0, PI, 1, 0);
    

    maths!

  • edited May 2015

    turns out that acos(b.dot(a)) = angleBetween(a, b)

    https://processing.org/reference/PVector_angleBetween_.html

  • Sorry. That's what I get for not reading the whole thread.

  • I'm graduated so no assignments from me :D Thanks. The acos does it. Although I really hoped it was possible in a more simple matter (as in less cpu cycles).

  • edited May 2015

    if you use angleBetween you don't need the normalise calls beforehand.

    a quick and dirty test of half a million calls gave the following results

    .552774692 Acos
    .476740581 AngleBetween
    

    (that's in seconds, so about one second for a million of your loops. is that fast enough?)

  • edited May 2015 Answer ✓

    ok, new method using atan2

    a.sub(b);
    b.sub(c);
    float a1 = atan2(a.y, a.x);
    float a2 = atan2(b.y, b.x);
    float dot4 = Math.abs((a1 - a2) - PI) / PI;
    

    no normalise, no dot, two calls to atan2 and some finagling of the value, less than half the time

    .555097545 DotAcos
    .476910295 DotAngle
    .193600252 DotAtan2
    
  • Who koogs that is really cool :) I use this for a pathfinder that looks at the angle of corners, I have to use this a lot more then a million times so this makes me happy :D

  • edited May 2015

    There is a bug in your code. If I change a to:

    a = new PVector(width-50, height/2);

    Then I get values exceeding the 1 range.

  • just dropping as a reference (for myself)

      a.sub(b);
      b.sub(c);
    
      a.normalize();
      b.normalize();
    
      float dot = b.dot(a);
    
      dot = map(acos(dot), 0, PI, 0, 1);
    
  • There is a bug in your code

    only one? 8)

    yeah, i did only test it with the original line and did wonder if another starting line would break it. the problem is that atan2 returns values from -PI to PI so adding them or subtracting them gives a possible range (or do i mean domain) of two whole circles.

    will have a play with it tonight, see what i can do.

  • cool :)

    O yeah it kinda sucks I can't undo a "accept answer".

  • ok, i think you can probably reduce the dot4 calculation... probably.

    // calculate direction change
    // acd 2015
    // for clankiller
    
    PVector a, b;
    int RAD = 200;
    
    void setup() {
      size(600, 600);
      noLoop();
    }
    
    void draw() {
    
      translate(width / 2, height / 2);
    
      float angle1 = TWO_PI * .125 * (int)random(8);
      a = new PVector(RAD * cos(angle1), RAD * sin(angle1));
      b = new PVector(0, 0);
    
      // black line is initial vector, finishes at centre
      background(255);
      strokeWeight(5);
      stroke(0);
      fill(0);
      line(a.x, a.y, b.x, b.y);
      ellipse(b.x, b.y, 10, 10);
    
      for (int i = 0 ;  i < 8 ; i++) {
        // tmp pvectors we can modify
        PVector tmpA = new PVector();
        PVector tmpB = new PVector();
        PVector tmpC = new PVector();
    
        // red line is second vector, starts at centre
        float angle2 = TWO_PI * .125 * i;
        PVector c = new PVector(RAD * cos(angle2), RAD * sin(angle2));
        strokeWeight(1);
        stroke(255, 0, 0);
        fill(255, 0, 0);
        line(b.x, b.y, c.x, c.y);
        ellipse(c.x, c.y, 5, 5);
    
        // calculate and print directions
        tmpA.set(a);
        tmpB.set(b);
        tmpC.set(c);
        tmpA.sub(tmpB);
        tmpB.sub(tmpC);
        tmpA.normalize();
        tmpB.normalize();
        float dot1 = tmpB.dot(tmpA);
        dot1 = map(acos(dot1), 0, PI, 1, 0);
        // red text is the original calculation
        text(dot1, c.x, c.y);
    
        float a1 = atan2(tmpA.y, tmpA.x);
        float a2 = atan2(tmpB.y, tmpB.x);
        float dot4 = Math.abs(PI - (((a1 + TWO_PI) - a2) % TWO_PI)) / PI;
        stroke(0, 0, 255);
        fill(0, 0, 255);
        // blue text is the non-acos version
        text(dot4, c.x, c.y + 20);
      }
      noLoop();
    }
    
    void keyPressed() {
      // press a key to redraw
      redraw();
    }
    
  • It's not a big deal for me to use the slower version :) But I appreciate what you are doing.

    Current one looks quite ok. I only have a NaN in red text when it points to the top left corner. However, i'm to tired to read code :)

  • hadn't noticed the NaN, i wonder why that is...

    luckily, the red text is the old dot product version, the new one is in blue.

  • @koogs, acos expects values in the range -1 to 1. Due to rounding errors the value is sometimes -1.0000001, producing the NaN.

Sign In or Register to comment.