Rotation and Easing

Hello

In my sketch I want to controle the rotation of a wheel and add a bit of easing to the rotation to give some feeling of weight to the wheel.
I have it doing what I want but, the easing is messed up by the gap between 0 and 360°

You will understand the problem very easily if you try the sketch

How would you solve this problem ?

here is the code

int cx, cy;
float radius = 400;
float rot = 0;

float hypo ;
float mySin ;   
float myCos ; 
float alpha;

float easing = 0.1;

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

  cx = width / 2;
  cy = height / 2;
}


void draw() {

  background(128);
  update();
  render();
}

void update() {

  hypo = dist(mouseX, mouseY, cx, cy);
  mySin = (mouseY - cy) / hypo;   
  myCos = (mouseX - cx)/ hypo; 

  if (mySin > 0) {
    alpha = acos(myCos);
  } else {
    alpha = (2*PI) - acos(myCos);
  }


  float targetRot = alpha;
  float dRot = targetRot - rot;

  rot += dRot * easing;
}



void render() {
  pushMatrix();
  translate(width/2, height/2); 

  rotate (rot);
  fill(255);
  ellipse(0, 0, radius, radius);

  fill(128);
  ellipse(150, 0, 50, 50);

  popMatrix();
}
1 Like

I think this should help:

3 Likes

I tried to calculate the shortest way and it seems to work:

  if(rot - alpha > alpha - (rot - TWO_PI)) {
    rot -= TWO_PI;
  } else if(alpha - rot > rot - (alpha - TWO_PI)) {
    rot += TWO_PI;
  }

  float targetRot = alpha;
  float dRot = targetRot - rot;

  rot += dRot * easing;

The logic of my code is: if rotating the angle all the way +TWO_PI or -TWO_PI makes distance to get shorter, than do it.

3 Likes

@phoebus , i could not stop play with that excellent idea,
but i could not solve the problem, but thanks to
@brunoruchiga can now show this version:

// https://discourse.processing.org/t/rotation-and-easing/5704
PVector myMouse = new PVector();
float rot = 0, alpha, dalpha, easing = 0.03;
//__________________________________________________________
void setup() {
  size(600, 600);
  stroke(120,120,120);
}
//__________________________________________________________
void draw() {
  myMouse.set( mouseX, mouseY, 0);
  myMouse.sub(width/2, height/2, 0);
  alpha = myMouse.heading();
  dalpha = (alpha-rot)/PI;
  if      ( dalpha < -1)       rot -= TWO_PI;  // @brunoruchiga
  else if ( dalpha > 1 )       rot += TWO_PI;
  rot += (alpha -rot) * easing;                // @phoebus
  // draw
  background(0, 0, 50);
  translate(width/2, height/2); 
  fill(170, 170, 0);
  ellipse(0, 0, 400, 400);
  rotate (rot);
  fill(170, 170, 170);
  ellipse(150, 0, 100, 100);
}

4 Likes

EXCALTY !

thank you so much @brunoruchiga, you nailed it !

:smiley:

1 Like

Hi,

I have been using this excellent sketch to simulate the behaviour of some rotating objects in Cinema 4D having converted the sketch to Python.

I am wondering if there is a way to increase the easing from - let’s say - 0.1 to 0.5 and then back from 0.5 to 0.1

I am trying to more realistically simulate some sort of acceleration/deceleration curve. I think this would be a way of doing it.

Any other suggestions?

You could take the easing values from a array that you fill from sin function

Or use sin directly together with map

you can try using the mouseY
means if top go slow, if bottom, go strong.

rot += (alpha -rot) * constrain( easing *height/(height-mouseY),0.01,0.1); 

Hi @kll,

Thanks for your reply. This doesn’t seem to be generating a changing value between 0.01 and 0.1

no, but is the effect that what you want?

play more

  float easing_var =  easing *height/(height-mouseY);
  print(easing_var);
  easing_var = constrain(easing_var,0.01,0.3);
  println(" limit "+easing_var);
  rot += (alpha -rot) * easing_var;                // @phoebus

but it feels to strong now

the constrain has 2 jobs,

  • limit the effect ( but should not be needed with good math )
  • limit against mouse leaves canvas
1 Like

I was tinkering with the original post to simulate angular acceleration\deceleration.
Just sharing…

It behaves very differently with rotation direction.

:slight_smile:

int cx, cy;
float radius = 400;
float rot = 0;

float hypo ;
float mySin ;   
float myCos ; 
float alpha;

float easing = .01f;

float mod;
float lastRot;

// GLV Added
float loc = 0;
float vel = 0;
float acc = TAU/2000;
int i;

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

public void setup() 
  {
  cx = width / 2;
  cy = height / 2;
  background(0);
  }


public void draw() 
  {
  //background(0);
  if (mousePressed) 
    {
    update();
    }
  render();
  }

public void update() 
  {
  hypo = dist(mouseX, mouseY, cx, cy);
  mySin = (mouseY - cy) / hypo;   
  myCos = (mouseX - cx)/ hypo; 

  if (mySin > 0) {
    alpha = acos(myCos);
  } else {
    alpha = (2*PI) - acos(myCos);
  }

  float targetRot = alpha;
  float dRot = targetRot - rot;
  //rot += dRot * easing;  
  
  rot += (loc/20); // Change this value for "speed"
  lastRot = rot;

  float trot = dRot;  
  loc += vel; //angle
  vel += acc; //angular acceleration
  if (loc < 0) 
    {
    vel = 0;
    loc = 0;
    }  
  
  if (loc >= trot) 
    {
    //vel = -vel;
    //loc = trot;
    vel = 0;
    loc = trot;
    }
  
  //println(rot, lastRot, rot-lastRot);
  println(loc, vel, acc);
  }

public void render() 
  {
 pushMatrix();
  translate(2*width/3, height/2); 

  strokeWeight(5);
  stroke(0, 255, 0);
  rotate (rot);
  fill(128);
  ellipse(0, 0, radius, radius);

  fill(0, 255, 0);
  ellipse(150, 0, 50, 50);
 popMatrix();
  
  stroke(255, 255, 0);
  strokeWeight(5);
  point(50*loc+width/4, i++);
  if (i>height) 
    {
    i = 0;
    background(0);
    }  
  }

:slight_smile:

1 Like

Hey @kll

Yes this is working. Okay, I get it - the ball has a kind of behaviour where it struggles to keep up against a kind of gravity pulling the ball back down. Nice, thanks for sharing this :slight_smile:

My real goal is to achieve a sketch where:

  • the ball registers the target

  • the ball begins to accelerate towards the target (ideally with an acceleration curve/non linear)

  • the ball is locked on to the target and can keep up with the target so long as the target doesn’t exceed the ball’s predetermined max speed

  • once the ball looses the target the ball decelerates

I think I have a way to go!

1 Like

@glv, that’s really cool! Thanks for sharing

1 Like

thanks,
that sounds a little like my old

auto shooter !! NOT A GAME !!

actually my first PVector exercise

  • search
  • move ( speed like easing )
  • shoot
  • next target …
// https://discourse.processing.org/t/bug-with-arraylist/5949
// 11/2018 kll  / my first game ever, good its not really a game
// make a arraylist of pvector points
// calc shortest distance to the next target, aim, fire + remove the point from array

int idt, pmax = 10;
ArrayList<PVector> points = new ArrayList<PVector>();     // list of points
PVector target = new PVector(0, 0);                       // position of cross line
boolean theend = false, shoot = false;

int k = 0, kmax = 30;  //firing circles
float tdist, newdist, speedf = 0.04;   // speed factor multiplied with distance gives realistic targeting movement

void setup() {
  size( 300, 300);
  for (int i = 0; i < pmax; i++) points.add(new PVector(random(-width/2, width/2), random(-height/2, height/2)));
  //for ( PVector p : points ) println(p);
  println("targets: "+points.size());
}

void draw_lines() {
  stroke(200, 0, 0);
  line(target.x, -height/2, target.x, height/2); 
  line(-width/2, target.y, width/2, target.y);
}

void draw_points() {
  for (int i = 0; i < points.size(); i++) { 
    if ( i == idt ) stroke(200, 0, 0);  
    else noStroke();
    fill(0, 200, 0);
    ellipse(points.get(i).x, points.get(i).y, 8, 8);
  }
}

int get_nearest() {
  int idx = -1;                               // init to not found
  tdist = width;                              // init to max
  for (int i = 0; i < points.size(); i++) {
    newdist = dist(points.get(i).x, points.get(i).y, target.x, target.y);
    if ( newdist < tdist ) { 
      tdist = newdist; 
      idx = i;
    }
  }
  return idx;                                 // give index of nearest target
}

void aim() {
  if ( idt > -1 ) {
    PVector waytotarget = points.get(idt).copy();
    waytotarget.sub(target);
    if ( waytotarget.mag() > 0.5 ) {                     // thats close enough
      waytotarget.setMag(waytotarget.mag()*speedf);      // calc faster to short step in that direction
      target = target.add(waytotarget);                  // change target
    } else { 
      if ( !shoot ) println("shoot at "+idt);            // only print 
      shoot = true;
      points.remove(idt);
    }
  } else { 
    println("i win, but now i am alone!"); 
    theend = true;
  }
}

void fire() {                                   // only if shoot = true
  fill(200, 100, 0);
  stroke(255, 0, 0); 
  strokeWeight(3);
  ellipse(target.x, target.y, 2*k, 2*k);        // growing red circle
  strokeWeight(1);
  k++;
  if ( k >= kmax ) {  
    shoot = false; 
    k = 0;
  }
}

void show_end() {
  fill(0, 200, 0);
  rect(-50, -25, 100, 40);
  fill(200, 0, 0);
  text(" I WIN ! ", -20, 0);                    // well, what a wonder if you play alone and targets not move
}

void draw() {
  background(200, 200, 0);
  translate(width/2, height/2);
  if ( !theend ) {
    draw_lines();                               // cross lines
    draw_points();                              // all points fix pos, same size / red stroke for next target
    idt = get_nearest();                        // next ( nearest ) target
    if ( !shoot )      aim();                   // not walk while firing
    else               fire();                  // some show / red circles
  } else show_end();                            // all over, no play again button
}

1 Like

Haha, that’s great.

Interesting, I guess a very important point that is missing from the original sketch is first determining how far away the target is.

Although, that is kind of happening with dalpha no?

  if      ( dalpha < -1)       rot -= TWO_PI;  // @brunoruchiga
  else if ( dalpha > 1 )       rot += TWO_PI;
  rot += (alpha -rot) * easing_var;                // @phoebus

If dalpha was more specifically targeted, that could be a way of simulating a different kind of acceleration?

if you talk about physical acceleration yes, both would be wrong

why not try @Chrisir s idea
of a sin speed acceleration-deceleration

Oops, totally missed his comment

here is sketch with lerp function


float mousePosX=100, mousePosY=100, 
  mousePosXFinal=100, mousePosYFinal=100;

boolean startUp=false; 

float amt; 
float maxAmt = 0.066; // WAS 0.025

int i=0; 
float initalDist1;

void setup() {
  size(500, 500);
  background(255);
  frameRate(20);
}

void draw() {
  background(255);

  // show grid 
  for (int i = 0; i < width; i += 50) {
    line(i, 0, i, height);
  }
  for (int i = 0; i < height; i += 50) {
    line(0, i, width, i);
  }

  // move red rect 
  float dist1  = dist(mousePosX, mousePosY, mousePosXFinal, mousePosYFinal);
  // Are we in the starting phase?
  if (startUp  &&
    amt<maxAmt && 
    dist1>initalDist1/2) {   // 
    // Yes, slowly accelerate 
    amt += 0.0011;
  } else { //  OR else if (amt>=0.025)
    // No, we slowly decelerate 
    if (startUp)
      println("hit");
    startUp=false; 
    amt = map (dist1, 0, 900, 0.06, maxAmt);
  }
  ellipse(i++, height-(float)amt*1900, 5, 5); 
  mousePosX = lerp(mousePosX, mousePosXFinal, amt);
  mousePosY = lerp(mousePosY, mousePosYFinal, amt);
  if (dist1<0.029) {
    if (amt>0)
      println("Thank you");
    amt=0;
    mousePosX = mousePosXFinal;
    mousePosY = mousePosYFinal;
  }
  // show red rect
  fill(255, 0, 0);
  rect(mousePosX, mousePosY, 50, 50);
  text(amt, 31, 31);
}

// ---------------------------------------------------------

void mousePressed() {
  // receive new target position

  // search mousePosXFinal  
  for (int i = 0; i < width; i += 50) {
    if (mouseX<=i+50) {
      mousePosXFinal = i;
      break;
    }
  }
  //
  // search mousePosYFinal
  for (int i = 0; i < height; i += 50) {
    if (mouseY<=i+50) {
      mousePosYFinal = i;
      break;
    }
  }

  // reset 
  startUp=true; 
  amt=0;
  i=0;

  float dist1  = dist(mousePosX, mousePosY, mousePosXFinal, mousePosYFinal); 
  println(dist1);
  initalDist1=dist1;
}
//
1 Like

Here is a another example:

:slight_smile: