How do you constrain an angle

edited October 2015 in How To...

Hi. I'm trying to constrain an angle. , measured in radians. But radians takes negative values so constrain() method will not work.

So, i tried converting radians to angles, constrain degrees and convert to radians again. But the result is the same.

a = radians(constrain(degrees(a), 90, 270));

Any clue?

Answers

  • Note: I'm using this to contrain arms an legs in a character. And code fails as soon as angle takes 180º value. It just jumps to 0 suddenly.

  • You mean this?

    println(radians(90));
    println(radians(270) + "\n");
    
    
    for (float a = 0; a < TWO_PI; a+=0.1) {
      float angle = constrain(a, HALF_PI, PI + HALF_PI);
      println("a = " +a+ " angle = " + angle);
      point(width/2 + cos(angle)*50, height/2 +  sin(angle)*50);
    }
    
    //or
    
    println(degrees(HALF_PI));
    println(degrees(PI + HALF_PI) + "\n");
    
    for (float a = 0; a < 360; a+=2) {
      float angle = constrain(a, 90, 270);
      println("a = " +a+ " angle = " + angle);
      point(width/2 + cos(radians(angle))*45, height/2 +  sin(radians(angle))*45);
    }
    
  • edited October 2015

    Thanks. Your code works just fine. I don't know why mine is failing!

    See, this is the simplified version:

     Arm arm;
    
    void setup() {
      size(800, 640); 
      arm = new Arm(width/2, height/2);  
      arm.constrain(0, 10, 280);
    }
    
    void draw() {
      background(0);
    
      // center of the screen
      fill(#FF0000);
      ellipse(width/2, height/2, 20, 20);
    
      arm.draw();  
    }
    
    void mouseDragged(){
      arm.mouseDragged();
    }
    
    void mouseReleased(){
      arm.mouseReleased();
    }
    
    
    
    
    
    class Arm {
    
      float boneLength = 70;
      Bone bone;
      boolean moving;
    
      Arm(float posX, float posY) {
    
    bone = new Bone(posX, posY, PI/2, boneLength);
      }
    
      void constrain(int index, float min, float max) {
    
    bone.restrictAngle( min, max );
      }
    
      void draw() {
    
    bone.draw();
      }  
    
      void mouseDragged() {
    
    if ( (dist(mouseX, mouseY, bone.tipX, bone.tipY) < (bone.r)*2) || moving ) {
    
      moving = true;
      bone.setAngle(atan2(mouseY - bone.y, mouseX - bone.x));
    }
      }
    
      void mouseReleased() {
    arm.moving = false;
      }
    }
    
    
    
    
    class Bone{
    
      color c;
      float r = 10; // joint radius
      float x;
      float y;
      float a = PI/2; // initial angle
      float aMax;
      float aMin; // max degrees value
      float tipX;
      float tipY;
      float length;
      Bone parent;
    
      Bone(float x, float y){
      this.x = x;
      this.y = y;
      length = 50;
      }
    
      Bone(float x, float y, float l){
      this.x = x;
      this.y = y;
      this.length = l;
      }
    
      Bone(float x, float y, float a, float l){
      this.x = x;
      this.y = y;
      setAngle(a);
      this.length = l;
      }
    
      Bone(Bone p, float l){
      parent = p;
      length = l;
      }
    
      /**
      * @param min degrees value
      * @param max degrees value
      */
      void restrictAngle(float min, float max){ 
    aMin = min;
    aMax = max;
      }
    
      /**
      * @param a radians value
      */
      void setAngle(float angle){
    
    if(aMax > 0){
      angle = constrain(degrees(angle), aMin, aMax);
    
      println("min: " + aMin + " max:" + aMax + " degrees:" + angle + " radians:" + radians(a));
      angle = radians(angle);
    } else {
    
      println(aMin + " " + aMax + " " + angle);
    }
    
    this.a = angle; 
      }
    
      void draw(float a){
    
    this.setAngle(a); // keep track of angle
    this.draw();
      }
    
      void draw() {
    
      strokeWeight(r*2);
      stroke(255, 100);  
    
      // these two are useful for child bone
      tipX = x + (cos(a) * length);
      tipY = y + (sin(a) * length);   
    
      pushMatrix(); // this moves the 0, 0 center to another place momentaneously. 
        translate(x, y);
        rotate(a); // rotate canvas   
        line(0, 0, length, 0);         
      popMatrix();
    
      // tip
      fill(#FFFF00);
      ellipse(tipX, tipY, 20, 20);
    
      }  
    }
    

    Try moving the yellow point to 180º

  • edited October 2015

    Sorry, I tried posting my code with "<"code">" tags but it's a mess. How do you do it?
    ...
    Nevermind about code block posting. Indenting did the trick.

    So, the problem with my code is this line, throwing negative numbers:

      atan2(mouseY - bone.y, mouseX - bone.x)
    

    Not sure how should i do it without atan2

  • I'm not understanding what atan2 does. According to docs, it "Calculates the angle (in radians)...". But radians are not supposed to take negative numbers, am I right?:

    While atan2 returns somthing else (don't know what)

    void draw(){
    
      background(0);
      stroke(240, 255, 30);
      line(width/2, height/2, mouseX, mouseY);
    
      float dx = mouseX-width/2;
      float dy = mouseY-height/2;
      float angle = atan2(dy, dx);  
      println( angle );
    }
    

    Anyway, how can I get numbers like in the graphic?

  • edited October 2015 Answer ✓

    atan2 returns an angle -PI to +PI or -180 to 180 degrees i.e

    East = 0
    South = PI/2
    West = PI and -PI
    North = 3*PI/2

    to get convert this to 0-2PI simple add 2PI if atan2 returns a negative value like this

    void draw() {
      background(0);
      stroke(240, 255, 30);
      line(width/2, height/2, mouseX, mouseY);
    
      float dx = mouseX-width/2;
      float dy = mouseY-height/2;
      float angle = atan2(dy, dx);
      angle = angle < 0 ? angle + TWO_PI : angle;  // Convert range to 0-TWO_PI
      println( angle );
    }
    
  • That's clever. Thanks.

    If anyone (like me) ignores the negative values of radians, check this: http://www.dummies.com/how-to/content/negative-and-positive-angles-cutting-a-circle.html

  • There was a mistake in my previous code at line 9 I have corrected it. Sorry about that :)

  • I know, don't worry.

    Just another question. Now it's easy to constrain value from 0 to 360 (thinking in degrees). But what if I need to constrain values from, let's say, 270 to 40? I tried setting 40º as 400 (360+40) but that didn't work. Same would happen with radian, going from 0 to 6.x right?

  • I'm a dummy. thanks for the link :)

  • edited October 2015 Answer ✓

    Constraining an angle to a sub range such as 270-40 is much more challenging and it would be good to specify the solution requirements from which we can design a reusable function to do the job.

    Function requirements
    1) The function requires three angles to be passed as parameters. The angle to constrain as well as the start and end angles for the range.
    2) All 3 angles to be stored as radians (more efficient)
    3) All 3 angles to be in the range 0 - 2π (i.e. 0 - 360 degrees)
    4) Visualized on a circle the constraning-arc is the area swept by a line rotating clockwise from the range start angle to the range end angle.
    5) if the range start angle is greater than the end angle it means the constraining-arc includes the angle 0 (zero)
    6) If the angle is outside the constraining-arc it returns the nearest of the 2 range limits, otherwise it retuns the angle unchanged.

    The following sketch demonstrates the function I have just described.

    float rangeStart = radians(270);
    float rangeEnd = radians(40);
    
    void setup() {
      size(400, 400);
    }
    
    void draw() {
      background(0);
      stroke(240, 255, 30);
      line(width/2, height/2, mouseX, mouseY);
    
      float dx = mouseX-width/2;
      float dy = mouseY-height/2;
      float angle = atan2(dy, dx);
      // Convert range to 0-TWO_PI
      angle = angle < 0 ? angle + TWO_PI : angle; 
      text("Actual angle = " + degrees(angle), 20, 20);
      angle = constrainAngle(angle, rangeStart, rangeEnd);
      text("Constrained angle = " + degrees(angle), 20, 40);
    }
    
    /**
     This function constrains an angle to a sub-range of the range 0-2Pi.
     All angles passed must be in the range 0-2Pi.
     Going clockwise from the start angle to the end angle defines the constraining
     range. This means it is permissible for the start angle to be greater than 
     the end angle e.g. a range 1.5Pi to 0.25Pi (270 to 45 degrees).
     If the angle is outside the range then the nearest range limit will be returned
     otherwise the angle is returned unchanged.
     */
    float constrainAngle(float angle, float start, float end) {
      if (start < end)  // rangge does not include 0
        return constrain(angle, start, end);
      // range includes 0
      if (angle < start && angle > end) {
        // Outside range so return nearest rangge limit
        if (abs(angle - start) < abs(angle - end))
          return start;
        else
          return end;
      }
      // Inside range
      return angle;
    }
    
Sign In or Register to comment.