Day11 - 物理模拟篇 - 弹跳球世界II - 成为Canvas Ninja ~ 理解2D渲染的精髓

继上一篇我们讲到向量类的建立,接着我们在这一篇文机会提到反射行为的模拟~
反射这种行为,在反射面铅直水平时比较单纯,所以大部分的Canvas弹跳球动画教程都是针对这种状况去做出来的。
但是在本篇的案例中,我们会提到在倾斜面上如何透过向量运算来计算反射的角度,同时也会讲解Canvas物理模拟和现实物理最大的差异之一:『帧间误差』,以及应对『帧间误差』的解决办法『归位(Reposition)』。

反射会是整个案例最复杂的部分,所以我会尽可能地描述仔细一点~

从数理几何观点来看反射

img

如果说反射面只是单纯的水平或垂直面,那麽反射的运算其实就很简单,就是把垂直於反射面的向量分量倒转而已。

但是如果今天反射面是倾斜的,那麽情况可就大不同了。

下图中描绘着一个即将碰撞到倾斜面的球。(坐标系是使用Canvas的坐标系)

img

从上面这张图,我们可以先定义两个有关该球碰撞倾斜面当下瞬间的假设,也就是所谓的『碰撞侦测(Collision Detection)』

  • 球的中心到倾斜面距离低於球的半径
  • 球的座标位置必须要介於倾斜面的两个端点之间

接着我们再来看看这张图。

img

假设我们一开始就知道球心的位置,和端点A&B的座标,这样我们就可以藉由这些数据去取得球心到斜面的垂直距离。

算法大致上是这样的:

// 已知球心座标和两端点座标
// 所以这样也就可以取得『向量AP』和『向量BP』还有反射面端点A到B形成的『向量AB』

let vectorAPP = vectorAP.project(vectorAB); //透过我们之前写的向量类中的投射(project)方法来取得『向量APP』
let dist = vectorAP.substract(vectorAPP).length(); // 透过向量差值来取得d的长度

如果对高中数学还有印象的人其实也可以用点到线公式去求取距离,不过那就会是另外一套算法了

接下来我们要讲讲所谓的『帧间误差』和『归位(reposition)』。

我们在前面有提到过,canvas的动画是藉由不断地清除画面後再重绘来达成的,所以说事实上球的动画并不是一个完全连续的运动。

意思就是说假设帧率为601/60 秒的时候球的位置在0, 而2/60秒的时候球的位置在0.1,但是实际上1/60~2/60秒之间,球是没有在运动的。

球的运动其实是『格进』,也就是说球在接近反射面的过程中,非常可能不会刚好有『球心与反射面距离 = 半径』的状况,多半情况下球可能会『插』进去了反射面里面。

这个就是所谓的『帧间误差』,而应对帧间误差的方式之一就是使用『归位(reposition)』:

假设球已经『插』进去反射面,则把它归位回正好『球心与反射面距离 = 球半径』的位置。

归位後的位置,我们可以透过球心和反射面距离d,利用向量计算和比例原理去计算出来,就像下面这样:

img

img

图片来自 Apress Physics for JavaScript Games Animation and Simulations, With HTML5 Canvas

事实上,帧间误差还有一个可能导致更大影响的问题在,那就是在球本身具有加速度时的状况。

在现实物理中,我们都知道若物体有受力,那麽它就必然的会产生加速度(牛顿第二定律),而这种行为如果要用Canvas模拟出来,就会受到帧间误差的影响。
我们在前面有提到,帧间误差的成因是因为canvas动画的物体实际上是格进式的运动,以『具有加速度的球碰撞墙壁』这个案例来看,球有很大的机会在判断撞上墙壁的那一刻,他会『』进去墙壁一小段距离,而这一小段距离实际上也会给球带来比预期还要多的速度加成(原本在现实生活中顶多是加速到球心和墙壁距离为半径而已),这种现象最终将会造成球每碰撞一次墙壁,就变得越快,而在碰撞多次的情况下可能就会造成动画走钟

通常上述这种状况的解法,我们可以为球加上一个值介於0~1之间的摩擦系数K,让球在每次反弹的时候去把当下的速度向量做一部分的缩减,也就是变相地去抵销帧间误差所带来的影响。

但是毕竟摩擦系数算是比较消极一点的做法,比较积极且精确的做法当然还是有。

这边这个公式是Apress Physics for JavaScript Games Animation and Simulations, With HTML5 Canvas 提出的一个计算方法(主要是用微积分求出来的近似值)

v'/v ≒ 1- (a加速度向量・h向量)/ v平方;


// 这边v是超过墙壁後那一帧的瞬时速度
// v'是reposition之後正确的速度

由於我们在上面有提到,我们能算得出『归位(reposition)』後的座标,那也就意味着我们可以计算出来归位前一帧到归位时所移动的长度,这样也就可以进一步计算出归位时的速度。

最後我们来到反射角度的计算,这部分就相对简单一些。

img

反射後的速度向量,我们在这边可以把他分割成沿着反射面方向的部分,和垂直於反射面的部分,而计算上则可以直接透过向量类的project方法来求得垂直於反射面的部分,最後再用扣的去取得沿着反射面方向的部分。

normalVelocity = dist.para(ball.velocity.projection(dist),-1);
tangentVelocity = ball.velocity.subtract(normalVelocity);

最後再把这两个向量用向量类的add方法合并起来就好~

小结

这边我们讲解完了反射的运算方法,在接下来的部分我们会继续带到斜向抛射的部分,然後在逐渐地去把我们在文章中描述的逻辑构建到程序中~


<<:  [DAY 11] Torchvision 简介

>>:  JavaScript Day11 - 回圈

# Day28--让commit像战国时代一样分分合合

上一篇我们学到怎麽使用Vim,还有修改commit message,这次要做的事情呢,就是要来合并跟...

【Day 5】Google Apps Script - 变数与函式呼叫与GS档的顺序影响

在专案里,所有的档案都预先被 import 在一起的,可直接呼叫其他 gs档里的变数与函式。gs档...

android studio 30天学习笔记-day 5-介绍retrofit(一)

在android开发,很常会用到网络资料请求的套件,retrofit是一种 HTTP 请求的工具,使...

D1-用 Swift 和公开资讯,打造投资理财的 Apps { 架设 Xcode 环境 }

我自己是个原生语言的开发者,所以选择 Apple 官方主推的 Swift 语言。开发工具也选 App...

C#_建立mdf失败_存取被拒

原本小弟我在WinCE上使用SqlCeEngine都挺好用的 奈何最近专案需要使用PC 於是我查了一...