Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calculate deceleration based on velocity and grid

I'm trying to calculate the decay or velocity in a requestFrame loop based on the fact that I know the velocity (v) and the distance I want to travel. I also know the ms for each frame.

So a simple decay algorithm is:

velocity *= 0.9

This slows down smooth and nicely, but I want the animated element to stop at a given position (snap to grid). So how can I calculate the deceleration accurately?

like image 999
David Hellsing Avatar asked Oct 09 '20 19:10

David Hellsing


People also ask

What is the formula for calculating deceleration?

The deceleration will be computed by dividing the final velocity minus the initial velocity, by the amount of time is taken for this drop in velocity.

How do you calculate deceleration from final velocity?

The deceleration will be computed by dividing the final velocity minus the initial velocity, by the amount of time is taken for this drop in velocity. The formula for acceleration can be used here, with a negative sign, to identify the deceleration value. The Formula for Deceleration It is computed as:

What is the UU of deceleration formula?

U = initial velocity, t = time taken, s = distance covered. Deceleration Formula is used to calculate the deceleration of the given body in motion. It is expressed in meter per second square (m/s 2).

What is meant by deceleration?

Deceleration occurs when acceleration has an opposite direction as the object's velocity. In physics, deceleration definition always refers to the slowing down of an object, which means that the magnitude of the velocity decreases. Deceleration is characterized by opposite directions between the acceleration and the velocity.

What is the acceleration calculator based on?

The acceleration calculator is based on three various acceleration equations, where the third is derived from Newton's work: 1 a = (v_f - v_i) / Δt, 2 a = 2 * (Δd - v_i * Δt) / Δt², 3 a = F / m,


5 Answers

I have to admit it is not clear if in your scenario there are 1, 2 or 3 dimensions, I'll speak about a linear movement; remember that in a multidimensional environment velocity, acceleration and distance are vectors.

I would use the uniform acceleration formulas:

S = v0 * t + 1/2 * a * t ^ 2

vt = v0 + a * t

From your question it seems that the acceleration and the time should be the outputs of the problem.

Last thing not clear in your question is that before you stated "I know the distance I want to travel" later you need the movement end on a grid, these two sentences seems to be in contrast... I would handle this rounding the ending position at the beginning of the computation process.

Now our problem has following inputs:

  1. D (distance), known
  2. v0 (initial velocity), known
  3. vt (ending velocity), know: 0
  4. dt (delta time), known: the time between two consecutive frames, expressed in seconds (not in milliseconds)

Let's start expressing the time in function of the acceleration (2nd formula)

t = (vt - v0) / a

vt = 0, so

t = - v0 / a

let's substitute this in the 1st formula

S = - v0 ^ 2 / a + 1/2 * a (- v0 / a) ^ 2 = - (v0 ^ 2) / (2 * a)

from here we can find the acceleration

a = - (v0 ^ 2) / (2 * S)

and from the 2nd formula the time

t = - v0 / a

As we said, at the beginning of the process, we need to round the distance to a position snap to the grid:

rD = roundDistanceToGrid(D);
a = - velocity * velocity / 2 / rD;
t = - velocity / a;

t will not be an integer multiplier of dt

from now on, until the time elapsed is lesser than t, at each frame just

velocity += a * dt;

on the first frame after the time has elapsed, to fix errors due to rounding, set velocity at zero and place the object exactly on the grid.

like image 79
Daniele Ricci Avatar answered Oct 30 '22 03:10

Daniele Ricci


For decay coefficient q and n steps (elementary time intervals) distance is sum of geometric progression

D = v0 * (1 - q ** n) / (1 - q)

We cand find q for given D, v0 and n (is the latter known?) with simple numerical methods.

Also note that velocity never becomes zero, so you perhaps have to use some threshold value to stop. If velocity diminishes linearly (constant deceleration) rather than exponentially, then things are simpler.

like image 38
MBo Avatar answered Oct 30 '22 02:10

MBo


Short answer:

d = 99  // Distance  
v = 11  // Velocity

// Negative acceleration is deceleration:
acceleration = -0.5 * v * v / (d - 0.5 * v)

Derivation:

Start with equation for motion with constant acceleration:

s1 = s0 + v0 + a*t*t/2

and equation for acceleration:

a = dv/dt (change in velocity over change in time)

and solve for a:

  1. We know dv = -v0 because the final velocity is 0.

  2. So t = dt = -v0/a

  3. Plug t into the first equation and solve for a to get:

    a = -0.5 * v0*v0 / (s1 - s0)

  4. s1 - s0 is simply the distance traveled d. For some reason I had to subtract half the velocity from d to get the correct results....


Proof by simulation: You can try entering different velocities and distances into the simulation below.

  • Note the final position is a little off because the equations assume continuous motion (infinitely small time steps), but requestFrame will result relatively large time steps.
  • For the same reason, acceleration must be calculated only once at the beginning of motion and saved. I tried recalculating the acceleration for every frame, and rounding/simulation errors get too large as the final position is reached.

function run() {
  console.log('Simulating:')
   
  d = getNumber('d')  // Distance
  v = getNumber('v')  // Velocity

  p = 0  // Position
  a = -0.5 * v * v / (d - 0.5 * v) // Acceleration
  
  log = [{p, v, a}]
  
  while (v > 0) {
    p = p + v;
    d = d - p;
    v = v + a;
    data = {p, v, a};
    console.log(data) // For StackOverflow console
    log.push(data)
  }
  console.table(log); // For browser dev console
}

function getNumber(id) {
  return Number(document.getElementById(id).value)
}
<div>Distance <input id=d value=10 /></div>
<div>Velocity <input id=v value=1 /></div>
<div><button onclick='run()'>Run Simulation (Open dev console first to get full data in a nicely formatted table)</button></div>
like image 26
Leftium Avatar answered Oct 30 '22 03:10

Leftium


Short answer: a = e**(-v0*dt/d), where d is your distance, a the decay constant, dt the time per frame and v0 the initial velocity.

Why? The algorithm you gave is an exponential decay. If you want to do it this way, you cant use the uniform acceleration equations in this answer. The implicit formulation v[n] = v[n-1] * a (e.g. with a=0.9 and v[0] = 1.0) at every frame n can be written explicitly as as v = v0*a**(n). Or better in terms of the time t as v = v0*a**(t/dt), where dt = 1/fps (frames per second) and t = n*dt (n = 1, 2, 3, ....). Note: this will never be 0! However, the traveling object still travels a finite distance. The distance traveled d is the integral of that function: d = v0*dt * a**t / ln(a). After some time, the object will be close to -v0*dt/ln(a). Solving for a gives the result above.

Note: this is an analytic result, your numeric result will be somewhere close to that.

like image 42
lebowski Avatar answered Oct 30 '22 03:10

lebowski


This is much more of a software-engineering problem than a math/physics problem. The math/physics is quite trivial. The difficult thing here is handling the varying frame/tick rate of the browser. Math/physics will not apply too practically for a problem advanced by discrete time steps of varying durations.

Here is some code which solves the problem; note you can click "destabilize" to watch it work under very unstable frame/tick rates (you'll see in the implementation that this simulation of lag is an honest one!)

Ideally hit the "full page" button:

let elem = document.querySelector('.model');
let rangeElem = document.querySelector('.range');
let fpsElem = document.querySelector('.fps');
let destabilizeElem = document.querySelector('.destabilize');

destabilizeElem.addEventListener('click', evt => {
  destabilizeElem.classList.toggle('active');
  evt.stopPropagation();
  evt.preventDefault();
});

let model = {
  pos: [ 0, 0 ],
  vel: [ 0, 0 ],
  startPos: [ 0, 0 ],
  range: 100
};
let reset = ({ startMs, range, vel, ang=0 }) => {
  
  // Start again with `range` representing how far the model
  // should travel and `vel` representing its initial speed.
  // We will calculate `velMult` to be a value multiplied
  // against `vel` each frame, such that the model will
  // asymptotically reach a distance of `range`
    
  let [ velX, velY ] = [ Math.sin(ang) * vel, Math.cos(ang) * vel ];
  
  // Note the box-shadow on `rangeElem` is 2px wide, so to
  // see the exact range it represents we should subtract
  // half that amount. This way the middle of the border
  // truly represents a distance of `range`!
  rangeElem.style.width = rangeElem.style.height = `${(range - 1) << 1}px`;
  rangeElem.style.marginLeft = rangeElem.style.marginTop = `-${range - 1}px`;
  elem.transform = 'translate(0, 0)';

  model.pos = [ 0, 0 ];
  model.vel = [ velX, velY ];
  model.startPos = [ 0, 0 ];
  model.range = range;
  
};

let ms = performance.now();
let frame = () => {
  
  let prevFrame = ms;
  let dms = (ms = performance.now()) - prevFrame;
  let dt = dms * 0.001;
  
  elem.style.transform = `translate(${model.pos[0]}px, ${model.pos[1]}px)`;
  
  // Now `velMult` is different every frame:
  let velMag = Math.hypot(...model.vel);
  let dx = model.pos[0] - model.startPos[0];
  let dy = model.pos[1] - model.startPos[1];
  let rangeRemaining = model.range - Math.hypot(dx, dy);
  let velMult = 1 - Math.max(0, Math.min(1, dt * velMag / rangeRemaining));
  
  model.pos[0] += model.vel[0] * dt;
  model.pos[1] += model.vel[1] * dt;
  model.vel[0] *= velMult;
  model.vel[1] *= velMult;
  
  fpsElem.textContent = `dms: ${dms.toFixed(2)}`;
  
  // Reset once the velocity has multiplied nearly to 0
  if (velMag < 0.05) {
    reset({
      startMs: ms,
      
      // Note that without `Math.round` results will be *visually* inaccurate
      // This is simply a result of css truncating floats in some cases
      range: Math.round(50 + Math.random() * 300),
      vel: 600 + Math.random() * 1200,
      ang: Math.random() * 2 * Math.PI
    });
  }
    
};
(async () => {
  while (true) {
    await new Promise(r => window.requestAnimationFrame(r));
    if (destabilizeElem.classList.contains('active')) {
      await new Promise(r => setTimeout(r, Math.round(Math.random() * 100)));
    }
    frame();
  }
})();
html, body {
  position: absolute;
  left: 0; right: 0; top: 0; bottom: 0;
  overflow: hidden;
}
.origin {
  position: absolute;
  overflow: visible;
  left: 50%; top: 50%;
}
.model {
  position: absolute;
  width: 30px; height: 30px;
  margin-left: -15px; margin-top: -15px;
  border-radius: 100%;
  box-shadow: 0 0 0 2px rgba(200, 0, 0, 0.8);
}
.model::before {
  content: ''; position: absolute; display: block;
  left: 50%; top: 50%;
  width: 4px; height: 4px; margin-left: -2px; margin-top: -2px;
  border-radius: 100%;
  box-shadow: 0 0 0 2px rgba(200, 0, 0, 0.8);
}
.range {
  position: absolute;
  width: 100px; height: 100px;
  margin-left: -50px; margin-top: -50px;
  border-radius: 100%;
  box-shadow: 0 0 0 2px rgba(200, 0, 0, 0.5);
}
.fps {
  position: absolute;
  right: 0; bottom: 0;
  height: 20px; line-height: 20px;
  white-space: nowrap; overflow: hidden;
  padding: 10px;
  font-family: monospace;
  background-color: rgba(0, 0, 0, 0.1);
}
.destabilize {
  position: absolute;
  right: 0; bottom: 45px;
  height: 20px; line-height: 20px;
  white-space: nowrap; overflow: hidden;
  padding: 10px;
  font-family: monospace;
  box-shadow: inset 0 0 0 4px rgba(0, 0, 0, 0.1);
  cursor: pointer;
}
.destabilize.active { box-shadow: inset 0 0 0 4px rgba(255, 130, 0, 0.9); }
<div class="origin">
  <div class="model"></div>
  <div class="range"></div>
</div>
<div class="destabilize">Destabilize</div>
<div class="fps"></div>

The trick here is to adapt the braking in realtime to the framerate.

In a discrete model where after every secsPerStep a position increments by a velocity, the velocity then multiplies by some brakingFactor, and there is some target distance to achieve, we know that:

brakingFactor = 1 - constantSecsPerStep * initialVelocity / distance

This of course only works if constantSecsPerStep is always constant. For a varying secsPerStep I used this formula instead:

updatedBrakingFactor = 1 - durationOfCurrentTick * currentVelocity / remainingDistance

It sounds like you wanted what I will call a "pure" solution, where there is no explicit "agenda" that establishes the location to which the decelerating object will snap (no data such as "intended destination" should exist). Unfortunately, I claim there must be at least some data which establishes this agenda, and that the model is not undergoing some arbitrary movement. The updatedBrakingFactor formula requires knowledge of the remainingDistance, not the initial distance. There will need to be data to derive this (in the code I decided to store the "start position" of the model, but the "start time" could also be used).

Note that mathematically, the velocity of the model never quite becomes 0 - therefore a heuristic is needed to approximate when the model has "arrived". I opted to wait for the instantaneous velocity to fall under some small threshold.

like image 35
Gershom Maes Avatar answered Oct 30 '22 03:10

Gershom Maes