Trying rotation angle easing, trouble crossing 0

I am trying to make a draggable clock hand that follows the mouse drag with easing. When dragging CW, after the drag crosses 0, the hand goes CCW to find the mouse, when dragging CCW, the hand will not cross 0.

I have tried a few attempts at creating conditions to measure the CW & CCW distance from the needle and using those to keep the motion going in the same direction as the drag as it goes over 0, but they ended up being very convoluted and didn’t work.

Ultimately I hope to make a clock that allows you to drag any of the hands if they are touched semi-precisely with hit testing, and then they will ease along with the drag and take the other hands with them as appropriate for a clock, like a toy clock. Then when the drag is complete, the hands would go back to moving with a timer class.

I would appreciate any suggestions as a starting point about what I would have to do to get the drag to take the hand around either direction and cross 0 without a hitch.

I am having the most trouble just conceptualizing how to solve this problem which doesn’t seem like it should be so complicated.

Here is a stripped down version that I have been working on.

PVector m = new PVector();
PVector mo = new PVector();
PVector pm = new PVector();
PVector po = new PVector();
PVector c = new PVector();
float faceR;
float offset = -PI/2;
//offset = 0;
float needleAng = offset;
float dropAng = PI*2/10;
float easeAmt = 0.1;
float easeAng;
float angDiff;
void setup() {
   size(400, 600);
   ellipseMode(RADIUS);
   c.set(width/2, height/2);
   faceR = width/2-50;
}
void draw() { 
   background(127);
   showFace();
   
   m.set(mouseX, mouseY);
   mo = PVector.sub(m, c);
   
   pm.set(pmouseX, pmouseY);
   po = PVector.sub(pm, c);
   
   dragAng = adjAngRange(mo.heading());
   
   easeAmt = 0.1;
   
   angDiff = dragAng-needleAng;
   
   if (mousePressed && angDiff < dropAng) {
      easeAng += easeAmt * (angDiff);
   }
   
   needleAng = easeAng;

   showText();
   showNeedle(needleAng);
   showHandle(dragAng);
}
boolean betweenAnglesRadians(float ang, float start, float end) {
   return end >= start
   ? ang >= start && ang <= end
   : (ang >= start && ang <= TWO_PI) || (ang >= 0 && ang <= end);
}
float adjAngRange(float angle) {
   return (angle>0) ? angle : PI*2+angle;
}
void lineStartRA(PVector start, float radius, float angle) {
   float x = cos(angle);
   float y = sin(angle);
   PVector end = new PVector(x, y);
   end.mult(radius);
   end.add(start);
   linePts(start, end);
}
void linePts(PVector a, PVector b) {
   line(a.x, a.y, b.x, b.y);
}
void circleCR(PVector center, float radius) {
   ellipse(center.x, center.y, radius, radius);
}
void showFace() {
   stroke(0);
   fill(200);
   circleCR(c, faceR);
}
void showNeedle(float angle) {
   strokeWeight(5);
   stroke(0);
   lineStartRA(c, faceR, angle);
}
void showHandle(float angle) {
   PVector handle = new PVector();
   float handleR = 40;
   float x = cos(angle);
   float y = sin(angle);
   handle.set(x, y);
   handle.mult(faceR-handleR);
   handle.add(c);
   noFill();
   strokeWeight(1);
   stroke(0);
   lineStartRA(c, faceR, angle);
   stroke(0);
   circleCR(handle, handleR);
}
String angDegStr(float angle) {
   return nfs(round(degrees(angle)), 3);
}
void showText() {
   String dragAngStr = "dragAng: " + angDegStr(dragAng);
   String needleAngStr = "needleAng: " + angDegStr(needleAng);
   String angDiffStr = "angDiff: " + angDegStr(angDiff);
   String easeAngStr = "easeAng: " + angDegStr(easeAng);
   
   String[] strings = { dragAngStr, needleAngStr, angDiffStr, easeAngStr };
   textSize(25);
   textAlign(RIGHT, TOP);
   for (int i=0; i<strings.length; i++) {
      text(strings[i], width-5, 5+i*25);
   }
}
1 Like

i think something like that was solved by @brunoruchiga

see/try
Rotation and Easing .

3 Likes

That looks promising, I’ll check it out. Thanks

Hello,

I have used “state changes” in the past and provided a simple example.

// GLV 2017-12-23
// Moving line in circle with mouse and detect state (quadrant) changes
// 2019-10-16
// Cleand it up a bit before posting to forums.

PFont font;

float Xo;                                                              
float Yo;
float radius = 200;                                                          
float x = 200, y = 0;
float theta;
int q, last_q;
int state_change = 0;
int col = color(255, 255, 0);;

public void settings()
  {  
  size(1000, 600); 
  }

public void setup()
  {
  // Numbers look better with a monospace font:
  font = loadFont("LucidaConsole-24.vlw");
  textFont(font);
  textSize(24);
  
  Xo = 2*width/3;                                                              
  Yo = height/2;
  stroke(col);
  }

public void draw()
  { 
  background(0);
  translate(Xo, Yo);
  
  noFill();                                                                 
  strokeWeight(3);                                                    
  stroke(255, 255, 0);
  ellipse(0, 0, 2*radius, 2*radius);                                          
  
  if((mousePressed) && (mouseButton == LEFT) && ((mouseX-Xo)*(mouseX-Xo) + (mouseY-Yo)*(mouseY-Yo)) <= radius*radius) 
    {
    theta = atan2(-(mouseY-Yo), mouseX-Xo);   
    x = radius*(cos(theta));                                            
    y = radius*(sin(-theta));
    }

// determine quadrant q from location of mouse over circle   
  if (y>0 && x>0) q = 1;
  else if (y>0 && x<0) q = 2;
  else if (y<0 && x<0) q = 3;
  else if (y<0 && x>0) q = 4;  
      
// "state machine" to add offsets on change of quadrant   
  if (q != last_q) 
    state_change = 1;
  else
    state_change = 0;

  if (state_change == 1)
    {
    println ("State", last_q, "to state", q);        
    
    if (last_q==3 && q==2) 
      {
      col = color(255, 0, 0);
      }
    else if (last_q==2 && q==3) 
      {
      col = color(255, 255, 0); 
      }
    
    else if (last_q==4 && q==1) 
      {
      col = color(0, 255, 0);
      }
    else if (last_q==1 && q==4) 
      {
      col = color(0, 0, 255); 
      } 
    }      
  last_q = q;

  // Display text
  fill(255);
  text("x:   " + nfp(x, 3, 5), -500, -200);
  text("y:   " + nfp(y, 3, 5), -500, -225);

  line(0,0, x,y);
  fill(col);
  circle(-(2*width/3-width/4), 0, 50); 
  }

This is old code and may need some tweaks; I see a few already like only using > and zero is not considered.

:slight_smile:

2 Likes

That works, thanks again.

PVector m = new PVector();
PVector mo = new PVector();
PVector pm = new PVector();
PVector po = new PVector();
PVector c = new PVector();
float faceR;
float offset = -PI/2;
//offset = 0;
float needleAng = offset;
float dropAng = PI*2/10;
dropAng = radians(15);
float easeAmt = 0.1;
float easeAng;
float rawAngDiff, angDiff;
void setup() {
   size(400, 600);
   ellipseMode(RADIUS);
   c.set(width/2, height/2);
   faceR = width/2-50;
}
void draw() { 
   background(127);
   showFace();
   
   m.set(mouseX, mouseY);
   mo = PVector.sub(m, c);
   
   pm.set(pmouseX, pmouseY);
   po = PVector.sub(pm, c);
   
   dragAng = adjAngRange(mo.heading());
   
   easeAmt = 0.1;
   
   angDiff = (dragAng-needleAng)/PI;
   rawAngDiff = abs(dragAng-needleAng);
   
   if(mousePressed) {
      if(angDiff < -1) {
         easeAng -= TWO_PI;  // @brunoruchiga
         
      } else if(angDiff > 1) {
         easeAng += TWO_PI;
      }

      if(rawAngDiff < dropAng) {
         easeAng += (dragAng - easeAng) * easeAmt; // @phoebus
      }
   }
   
   needleAng = easeAng;
   
   showText();
   showNeedle(needleAng);
   showHandle(dragAng);
}
float adjAngRange(float angle) {
   return (angle>0) ? angle : PI*2+angle;
}
void lineStartRA(PVector start, float radius, float angle) {
   float x = cos(angle);
   float y = sin(angle);
   PVector end = new PVector(x, y);
   end.mult(radius);
   end.add(start);
   linePts(start, end);
}
void linePts(PVector a, PVector b) {
   line(a.x, a.y, b.x, b.y);
}
void circleCR(PVector center, float radius) {
   ellipse(center.x, center.y, radius, radius);
}
void showFace() {
   stroke(0);
   fill(200);
   circleCR(c, faceR);
}
void showNeedle(float angle) {
   strokeWeight(5);
   stroke(0);
   lineStartRA(c, faceR, angle);
}
void showHandle(float angle) {
   PVector handle = new PVector();
   float handleR = 40;
   float x = cos(angle);
   float y = sin(angle);
   handle.set(x, y);
   handle.mult(faceR-handleR);
   handle.add(c);
   noFill();
   strokeWeight(1);
   stroke(0);
   lineStartRA(c, faceR, angle);
   stroke(0);
   circleCR(handle, handleR);
}
String angDegStr(float angle) {
   return nfs(round(degrees(angle)), 3);
}
String angRadStr(float angle) {
   return nfs(round(angle*10)/10, 1, 1);
}
void showText() {
   String dragAngStr = "dragAng: " + angDegStr(dragAng);
   String needleAngStr = "needleAng: " + angDegStr(needleAng);
   String angDiffStr = "angDiff: " + angDegStr(angDiff);
   String rawAngDiffStr = "rawAngDiff: " + angRadStr(rawAngDiff);
   String easeAngStr = "easeAng: " + angDegStr(easeAng);
   
   String[] strings = { dragAngStr, needleAngStr, rawAngDiffStr, angDiffStr, easeAngStr };
   textSize(25);
   textAlign(RIGHT, TOP);
   for (int i=0; i<strings.length; i++) {
      text(strings[i], width-5, 5+i*25);
   }
}
2 Likes