0

I am aiming to incorporate easing into time based move in my project and may need some help doing so.

For the time being i am using simple formula of x pixels per second.

...
speed: 100,
now: undefined,
delta: undefined,
then: undefined,
setDelta: function() {
    this.now = Date.now();
    this.delta = (this.now - this.then) / 1000;
    this.then = this.now;
},
...

var slice = this.speed * this.delta;
this.x += Math.cos(rad) * slice;
this.y += Math.sin(rad) * slice;

By doing so my object is moving with 100 pixels per second. The animation however is very boring, therefore an idea to make it more interesting by making it to start slow, accelerate to half of the distance and then start slowing again until it reaches destination.

I found this list of easing functions for javascript click.

I think that the one that seems accurate would be some smooth sine, like this one:

easeInOutSin: function (t) {
    return (1 + Math.sin(Math.PI * t - Math.PI / 2)) / 2;
}

the problem is however that i cant figure out how to "connect" this formula to my code (presented above).

On the link the guys claim that t is parameter from 0 to 1 and i am thinking that probably what needs to vary is the speed. Maybe someone could help out.

this is a demo snippet for experimenting:

let distance = (p) => Math.sqrt((p.x - p.dx) * (p.x - p.dx) + (p.y - p.dy) * (p.y - p.dy)),
 rftv = (p) => Math.atan2(p.dy - p.y, p.dx - p.x);

let cvs = document.createElement('canvas'),
 ctx = cvs.getContext('2d'),
 w = cvs.width = 700,
 h = cvs.height = 200,
 cx = w / 2,
 cy = h / 2;

let obj = {
 x: 100,
 y: cy,
 speed: 100,
 dx: 600,
 dy: cy,
 run: function() {
  if(!this.moving) { return; }
  let d = distance(this);
  if(d < 1) {
   this.end();
  }
  this.setDelta();
  var slice = this.speed * this.delta;
  let rad = rftv(this);
  this.x += Math.cos(rad) * slice;
  this.y += Math.sin(rad) * slice;
 },
 now: undefined,
 delta: undefined,
 then: undefined,
 setDelta: function() {
  this.now = Date.now();
  this.delta = (this.now - this.then) / 1000;
  this.then = this.now;
 },
 moving: false,
 start: function() {
  this._started_ = Date.now();
  this.then = Date.now();
  this.moving = true;
 },
 end: function() {
  this.moving = false;
  console.log( Date.now() - this._started_, 'should be close to 5000' );
 }
};

let render = () => {
 ctx.fillStyle = '#ccc';
 ctx.fillRect(0, 0, w, h);

 ctx.beginPath();
 ctx.arc(obj.x, obj.y, 10, 0, Math.PI * 2);
 ctx.closePath();
 ctx.strokeStyle = 'red';
 ctx.stroke();
 obj.run();

 requestAnimationFrame(render);
};

document.body.appendChild(cvs);
render();

obj.start();
Mevia
  • 1,517
  • 1
  • 16
  • 51

1 Answers1

1

The easing function you picked is just in function of time. It means it returns a ratio from 0 to 1 depending on a time that is also in a range from 0 to 1. It means you have to calculate the ratio of time elapsed compared to the total animation time you want. Then to calculate the position you need to apply the returned ratio to the total distance you want to go (this.dx - this.startX) and add it to start position.

Note that in the following examples I ignored your rad and this.then calculations, i didn't really see what you meant with rad, and as you see easing must be in function of a total distance to go and total animation time. So there is no notion of speed either, or you have to apply it to the total distance/animation time instead.

let distance = (p) => Math.sqrt((p.x - p.dx) * (p.x - p.dx) + (p.y - p.dy) * (p.y - p.dy)),
 rftv = (p) => Math.atan2(p.dy - p.y, p.dx - p.x),
  easeInOutSin = function (t) {
    return (1 + Math.sin(Math.PI * t - Math.PI / 2)) / 2;
  };

let cvs = document.createElement('canvas'),
 ctx = cvs.getContext('2d'),
 w = cvs.width = 700,
 h = cvs.height = 200,
 cx = w / 2,
 cy = h / 2;

let obj = {
 x: 100,
  startX: 100,
 y: cy,
 //speed: 100,
 dx: 600,
 dy: cy,
 run: function() {
  if(!this.moving) { return; }
  let d = distance(this);
  if(d < 1) {
   this.end();
  }
  this.setDelta();
  /*var slice = this.speed * this.delta;
  let rad = rftv(this);
  this.x += Math.cos(rad) * slice;*/
  this.x = this.startX + (this.delta * (this.dx - this.startX));
  //this.y += Math.sin(rad) * slice;
 },
 now: undefined,
 delta: undefined,
 //then: undefined,
 setDelta: function() {
  this.now = Date.now();
  this.delta = easeInOutSin( (this.now - this._started_) / 5000 ); //(this.now - this.then) / 1000;
  //this.then = this.now;
 },
 moving: false,
 start: function() {
  this._started_ = Date.now();
  this.then = Date.now();
  this.moving = true;
 },
 end: function() {
  this.moving = false;
  console.log( Date.now() - this._started_, 'should be close to 5000' );
 }
};

let render = () => {
 ctx.fillStyle = '#ccc';
 ctx.fillRect(0, 0, w, h);

 ctx.beginPath();
 ctx.arc(obj.x, obj.y, 10, 0, Math.PI * 2);
 ctx.closePath();
 ctx.strokeStyle = 'red';
 ctx.stroke();
 obj.run();

 if(obj.moving){ requestAnimationFrame(render); }
};

document.body.appendChild(cvs);

obj.start();

render();

Here is a second example with a more advanced easing function adapted from this answer that takes 4 parameters: time elapsed, starting and ending values and total animation time. Returned value is directly your x position. EDIT: fixed applied parameters, should be 0 and total distance, and then you add it to starting position.

let distance = (p) => Math.sqrt((p.x - p.dx) * (p.x - p.dx) + (p.y - p.dy) * (p.y - p.dy)),
 rftv = (p) => Math.atan2(p.dy - p.y, p.dx - p.x),
    easeInOutSine = (t, startVal, endVal, totalTime) => (-endVal/2 * (Math.cos(Math.PI*t/totalTime) - 1) + startVal);

let cvs = document.createElement('canvas'),
 ctx = cvs.getContext('2d'),
 w = cvs.width = 700,
 h = cvs.height = 200,
 cx = w / 2,
 cy = h / 2;

let obj = {
 x: 100,
    startX: 100,
 y: cy,
 //speed: 100,
 dx: 600,
 dy: cy,
 run: function() {
  if(!this.moving) { return; }
  let d = distance(this);
  if(d < 1) {
   this.end();
  }
  this.setDelta();
  /*var slice = this.speed * this.delta;
  let rad = rftv(this);
  this.x += Math.cos(rad)  * slice;*/
  this.x = this.startX + this.delta;
  //this.y += Math.sin(rad) * slice;
 },
 now: undefined,
 delta: undefined,
 //then: undefined,
 setDelta: function() {
  this.now = Date.now();
  this.delta = easeInOutSine((this.now - this._started_), 0, (this.dx - this.startX), 5000);//(this.now - this.then) / 1000;
  //this.then = this.now;
 },
 moving: false,
 start: function() {
  this._started_ = Date.now();
  this.then = Date.now();
  this.moving = true;
 },
 end: function() {
  this.moving = false;
  console.log( Date.now() - this._started_, 'should be close to 5000' );
 }
};

let render = () => {
 ctx.fillStyle = '#ccc';
 ctx.fillRect(0, 0, w, h);

 ctx.beginPath();
 ctx.arc(obj.x, obj.y, 10, 0, Math.PI * 2);
 ctx.closePath();
 ctx.strokeStyle = 'red';
 ctx.stroke();
 obj.run();

 if(obj.moving){ requestAnimationFrame(render); }
};

document.body.appendChild(cvs);

obj.start();

render();

Hope you understand better how it works, good luck!

Additional note: I also inversed obj.start(); and render(); and added a condition to requestAnimationFrame to avoid endless loop.

Kaddath
  • 5,933
  • 1
  • 9
  • 23
  • i see now, so the difference is that now instead of speed i just need total time needed for travel. that is very nice, sir, which method would you recommend first or second? – Mevia Mar 11 '19 at 11:35
  • As you say yourself, and implied in the answer, is that these are fundamentally exclusive approaches. You cannot have easing and a constant speed that is used to calculate a duration. – Kaddath Mar 11 '19 at 11:36
  • Actually it depends on your goal, the first one needs calculations each time applied, the second one is easier to parametrize with pre-calculated values, I don't have enough math expertise to say which one is more optimized – Kaddath Mar 11 '19 at 11:38
  • ok if i may i have one last question, (without changing your answer), how would look the easing function for starting slow but always growing until reaching target (without slowing down at the end), function for the second example with all params? i was thinking to use this kind of function for bullets :) – Mevia Mar 11 '19 at 12:13
  • In this case, look for for "easing in" function (without the "out"), that will be a different function – Kaddath Mar 11 '19 at 12:32
  • sure thing, thank you for the answers that is a lot of solid work ;) – Mevia Mar 11 '19 at 12:48