turn left or right

edited January 2014 in How To...

Hi all, I am having some trouble with this:
I am using the PVector class to define some moving elements in a sketch.
So an entity has:

PVector pos; //position
PVector vel; //velocity
PVector tar; //target position
PVector towardsTar; //direction from element to target


Basic calculations:

float heading = vel.heading(); //ok, I have the heading angle of my element
towardsTar.set(tar);
towardsTar.sub(pos); // pos + towardsTar = tar  -->  towardsTar = tar - pos so I have my desired direction
towardsTar.normalize();

Now I have these two vectors (heading and towardsTar) and I want to find the angle between them, inorder to know if the element has to turn clockwise or counterclockwise inorder to head towards the target.
If I use this:

float angle = PVector.angleBetween(heading, towardsTar);

I will get the right angle, but it will always be positive.
So I will not know if the entity has to turn the one or the other way.
I have managed to make a workaround for that, but my algorithm is not so good...

Is there a good and elegant way to do that? Perhaps something I have missed from the PVector class?
Please do not refer me to a library, I am just looking for a simple way to do that by defining a small function at most.
Thanks in advance, Kostas

Answers

  • edited January 2014

    Just check whether the dot product of towardsTar and the normal of heading is negative or positive:

    if(towardsTar.dot(heading.y, -heading.x, 0) < 0) // By swapping x & y (and making x negative) you get the right hand normal
        println("It's going right!"); // Replace with something useful
    else
        println("It's going left!"); // Replace with something useful
    

    And a small example sketch:

    PVector pos = new PVector();
    PVector heading = new PVector();
    PVector towardsTar = new PVector();
    float angle = 0;
    
    
    void setup() {
    
        size(400, 400);
        strokeWeight(4);
        stroke(#ffffff);
    
        pos.set(width / 2, height / 2);
        heading.set(0, -1);
    
    }
    
    
    void draw() {
    
        towardsTar.set(mouseX, mouseY);
        towardsTar.sub(pos);
    
        angle += 0.01;
        heading.set(sin(angle), cos(angle));
    
        background(#000000);
    
        fill(#ffffff);
        if(towardsTar.dot(heading.y, -heading.x, 0) < 0)
            text("RIGHT", 10, 10 + textAscent());
        else
            text("LEFT", 10, 10 + textAscent());
    
        fill(#999999);
        ellipse(pos.x, pos.y, 200, 200);
        line(pos.x, pos.y, pos.x + heading.x * 100, pos.y + heading.y * 100);
    
    }
    
  • This would definately make sense, but unfortunately the PVector.angleBetween() function only returns positive values, and (I forgot to mention) also only values from 0 to PI. So if the anlge between them is 3*PI/2, then it will return PI/2 (instead of -PI/2). I dont know why that is, but this is my problem, actually... thanks anyway

  • Answer ✓

    Yeah, sorry - should test code before posting. :D I corrected my answer.

  • Answer ✓

    Very similar to the other solution but created a small function that accepts a heading and a target vector and calculates the 'nearest' side. Anyway play with the sketch it soon becomes obvious how it works.

    final int CLOCKWISE = 1; // Left
    final int ANTI_CLOCKWISE = -1; // Right
    
    PVector h, t;
    int cx, cy;
    
    public void setup() {
      size(400, 400);
      cx = width/2;
      cy = height/2;
      h = PVector.random2D();
      h.mult(150);
      t = PVector.random2D();
    }
    
    public void draw() {
      background(255);
      stroke(100, 100, 255);
      strokeWeight(2);
      line(cx, cy, cx + h.x, cy + h.y);
      ellipse(cx + h.x, cy + h.y, 4, 4);
    
      if (sign(h, t) == CLOCKWISE)
        stroke(100, 255, 100);
      else
        stroke(255, 100, 100);
      strokeWeight(1);
      line(cx, cy, cx + t.x, cy + t.y);
    }
    
    public void mouseMoved() {
      t.x = mouseX - cx;
      t.y = mouseY - cy;
    }
    
    public int sign(PVector heading, PVector target) {
      if (heading.y*target.x > heading.x*target.y)
        return CLOCKWISE;
      else
        return ANTI_CLOCKWISE;
    }
    
  • I still have to figure out how (why) your answers work... even though they do! Thanks alot, guys!

  • edited January 2014

    @quark: Haha nice! :D

    Even shorter:

    boolean isLeft(PVector heading, PVector target) {
        return heading.y * target.x > heading.x * target.y;
    }
    
    boolean isRight(PVector heading, PVector target) {
        return heading.y * target.x <= heading.x * target.y;
    }
    
  • edited January 2014

    Well, made my own shorter version too: >:)

    static final int sign(PVector head, PVector target) {
      return (int) Math.signum(head.y*target.x - head.x*target.y);
    }
    

    And another 1: *-:)

    void draw() {
      // ...
      stroke(sign(h, t) == CLOCKWISE ? #64FF64 : #FF6464);
      // ...
    }
    
  • Okay - the last one - I promise! :D

    boolean isLeft(PVector heading, PVector target) {
        return (Float.floatToRawIntBits(heading.y * target.x - heading.x * target.y) >> 31) == 0;
    }
    

    Additionally one little reference that explains the dot product quite well.

  • Sorry to bother again, but this doesn't work... Perhaps I am too tired to see why, but It has been a pain for sometime, so I thought to share once more...

    FlockingGame g;
    
    void setup(){
      size(600,600);
      g = new FlockingGame(20);
    }
    
    void draw(){
      background(0);
      g.update();
    }
    
    class FlockingGame{
      ArrayList birds;
      Target t;
      FlockingGame(int num){
        t = new Target();
        birds = new ArrayList();
        for(int i = 0; i <num; i++){
          birds.add(new Bird());
        }
      }
      void update(){
        t.update();
        for(int i = 0; i<birds.size(); i++){
          Bird b = (Bird) birds.get(i);
          b.update(t.getPos());
        }
      }
    }
    
    class Bird{
      PVector pos; 
      PVector vel; //velocity
      PVector tar;
      float tsa; //turn step angle
      Bird(){
        pos = new PVector();
        tar = new PVector();
        pos.x = random(0,600);
        pos.y = random(0,600);
        vel = PVector.random2D();
        tsa = PI/50;
      }
      void update(PVector inTar){
        tar.set(inTar);
        float r = tsa * sign(vel,tar);
        vel.rotate(r);
        pos.add(vel);
        show();
      }
    
      int sign(PVector vel, PVector tar) {
        int a = (int) Math.signum(vel.y*tar.x - vel.x*tar.y);
        return a;
      }
    
      void show(){
        pushMatrix();
          translate(pos.x,pos.y);
          rotate(vel.heading()+PI/2);
          beginShape();
            vertex(-3,0);
            vertex(0,-10);
            vertex(3,0);
          endShape(CLOSE);
        popMatrix();
      }
    }
    
    class Target{
      PVector pos;
      Target(){
        pos = new PVector(mouseX,mouseY,0);
      }
      void update(){
        /*pos.x = mouseX;
        pos.y = mouseY;*/
        pos.x = 300;
        pos.y = 300;
        pos.z = 0;
        show();
      }
      PVector getPos(){
        return pos;
      }
      void show(){
        fill(255,0,0);
        noStroke();
        ellipse(pos.x,pos.y,10,10);
      }
    }
    
  • ok, I found out what was wrong...
    I was comparing vel to tar, while I should be coparing vel to towardsTar (tar-pos)...
    fixed it for now... thanks for all the help again

Sign In or Register to comment.