根据速度和网格计算减速度

Posted

技术标签:

【中文标题】根据速度和网格计算减速度【英文标题】:Calculate deceleration based on velocity and grid 【发布时间】:2020-10-09 19:10:46 【问题描述】:

我正在尝试计算 requestFrame 循环中的衰减或速度,因为我知道速度 (v) 和我想要行进的距离。我也知道每一帧的ms

所以一个简单的衰减算法是:

velocity *= 0.9

这会减慢流畅且很好,但我希望动画元素停止在给定位置(对齐网格)。那么如何准确计算减速度呢?

【问题讨论】:

我的回答不清楚吗? 多少维?一个或多个? 【参考方案1】:

我不得不承认,在您的场景中是否有 1、2 或 3 个维度,我将谈论线性运动;请记住,在多维环境中,速度、加速度和距离是向量。

我会使用匀加速公式:

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

vt = v0 + a * t

从你的问题看来,加速度和时间应该是问题的输出。

你的问题中最后不清楚的是,在你说“我知道我想走的距离”之前,你需要网格上的运动结束,这两个句子似乎是对比的......我会处理这个在计算过程开始时舍入结束位置。

现在我们的问题有以下输入:

    D(距离),已知 v0(初始速度),已知 vt(结束速度),知道:0 dt(增量时间),已知:两个连续帧之间的时间,单位为秒(不是毫秒)

让我们开始用加速度的函数来表达时间(第二个公式)

t = (vt - v0) / a

vt = 0,所以

t = - v0 / a

让我们在第一个公式中替换它

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

从这里我们可以找到加速度

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

从第二个公式开始

t = - v0 / a

正如我们所说,在过程开始时,我们需要将距离四舍五入到对齐网格的位置:

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

t 不会是dt 的整数乘数

从现在开始,直到经过的时间小于t,在每一帧只是

velocity += a * dt;

在时间过去后的第一帧,为了修复由于舍入引起的错误,将速度设置为零并将对象准确地放置在网格上。

【讨论】:

使用物理。这才是真正的交易。 我相信这需要连续帧之间的时间是某个恒定值,但事实并非如此。【参考方案2】:

对于衰减系数qn 步长(基本时间间隔)距离是几何级数之和

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

对于给定的Dv0n(后者已知吗?),我们可以通过简单的数值方法找到q

还请注意,速度永远不会变为零,因此您可能必须使用一些阈值来停止。如果速度呈线性递减(恒定减速)而不是指数递减,那么事情就简单了。

【讨论】:

n 无法得知 - 没有任何 js 环境可以保证在一个时间间隔内发生多少渲染滴答声。【参考方案3】:

简答:

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

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

推导:

从恒加速度运动方程开始:

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

和加速度方程:

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

求解a:

    我们知道dv = -v0,因为最终速度为 0。

    所以t = dt = -v0/a

    t 代入第一个方程并求解a 得到:

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

    s1 - s0 只是d 的距离。出于某种原因,我不得不从d 中减去一半的速度才能得到正确的结果....


模拟证明: 您可以尝试在下面的模拟中输入不同的速度和距离。

请注意,由于方程假设连续运动(无限小的时间步长),最终位置有点偏离,但requestFrame 会产生相对较大的时间步长。 出于同样的原因,加速度必须在运动开始时只计算一次并保存。我尝试重新计算每一帧的加速度,当到达最终位置时,舍入/模拟误差变得太大。

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 *** 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>

【讨论】:

【参考方案4】:

简答:a = e**(-v0*dt/d),其中d 是您的距离,a 是衰减常数,dt 是每帧时间,v0 是初始速度。

为什么? 您给出的算法是指数衰减。如果你想这样做,你不能使用this answer中的匀加速方程。 每帧n 的隐式公式v[n] = v[n-1] * a(例如,a=0.9 和v[0] = 1.0)可以显式写为v = v0*a**(n)。或者在时间方面更好tv = v0*a**(t/dt),其中dt = 1/fps(每秒帧数)和t = n*dtn = 1, 2, 3, ....)。 注意:这永远不会是 0!然而,行进的物体仍然行进有限的距离。 经过d 的距离是该函数的积分:d = v0*dt * a**t / ln(a)。一段时间后,该对象将接近-v0*dt/ln(a)。求解a 得到上述结果。

注意:这是一个分析结果,您的数字结果将接近该结果。

【讨论】:

【参考方案5】:

这更像是一个软件工程问题,而不是数学/物理问题。数学/物理非常琐碎。这里的困难在于处理浏览器的不同帧/滴答率。对于由变化持续时间的离散时间步长推进的问题,数学/物理不会太实用。

这里有一些解决问题的代码;请注意,您可以单击“去稳定化”来观看它在非常不稳定的帧/滴答率下工作(您会在实现中看到这种延迟模拟是真实的!)

最好点击“整页”按钮:

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) << 1px`;
  rangeElem.style.marginLeft = rangeElem.style.marginTop = `-$range - 1px`;
  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>

这里的技巧是根据帧速率实时调整制动。

在一个离散模型中,在每个secsPerStep 一个position 递增一个velocity 之后,velocity 然后乘以一些brakingFactor,并且有一些目标distance 要实现,我们知道:

brakingFactor = 1 - constantSecsPerStep * initialVelocity / distance

这当然只有在constantSecsPerStep 始终保持不变的情况下才有效。对于不同的secsPerStep,我使用了这个公式:

updatedBrakingFactor = 1 - durationOfCurrentTick * currentVelocity / remainingDistance

听起来您想要我称之为“纯粹”的解决方案,其中没有明确的“议程”来确定减速对象将捕捉到的位置(不应存在诸如“预期目的地”之类的数据)。不幸的是,我声称必须至少有一些数据可以建立这个议程,并且该模型没有经历一些随意的运动。 updatedBrakingFactor 公式需要知道remainingDistance,而不是初始距离。需要有数据才能得出这个(在代码中我决定存储模型的“开始位置”,但也可以使用“开始时间”)。

请注意,从数学上讲,模型的速度永远不会完全变为0 - 因此需要一个启发式方法来近似模型“到达”时的情况。我选择等待瞬时速度低于某个小的阈值。

【讨论】:

【参考方案6】:

您可以使用您建议的公式velocity *= r 来实现您的目标。但是,从理论上讲,您的对象将需要无限的时间才能行进您想要的距离d,因为使用连续乘法,速度实际上永远不会达到零。实际上,它会在达到您的值可以认为大于零的最低值后达到零,但这也会花费很多时间。 为了得到你需要的值r,从速度V0开始,假设你的帧的时间间隔是ms,值r可以计算为:

r = 1 - V0 * ms / D;

还有另一种选择,每帧将速度降低一个常数值dv,这个值可以计算为:

dv = ms * Math.pow(V0, 2) / (2 * D - ms * V0);

第二种情况的行进距离可能并不总是D,只有当值2 * D / ms / V0 是整数时才会发生这种情况。否则物体会移动额外的距离,如果速度变为负值,您必须确保停止运动,您可以在最后一步对速度进行修改以解决此问题。

数学细节可以在my answer to that question找到。

【讨论】:

以上是关于根据速度和网格计算减速度的主要内容,如果未能解决你的问题,请参考以下文章

mfix中输出DEM颗粒的固相速度到网格

gambit中的体网格skewness大于0.97怎么修改?若仍然存在大于0.97的网格能在fluent中计算么

高性能几何多重网格与 GPU 加速

Java案例:根据速度计算刹车的安全距离

star ccm中 wall y+是啥意思

GeoServer发布WMTS详细过程