继上一篇我们讲到向量类
的建立,接着我们在这一篇文机会提到反射
行为的模拟~
反射
这种行为,在反射面
为铅直
或水平
时比较单纯,所以大部分的Canvas弹跳球动画教程都是针对这种状况去做出来的。
但是在本篇的案例中,我们会提到在倾斜面
上如何透过向量运算来计算反射的角度,同时也会讲解Canvas物理模拟和现实物理最大的差异之一:『帧间误差
』,以及应对『帧间误差
』的解决办法『归位
(Reposition)』。
反射
会是整个案例最复杂的部分,所以我会尽可能地描述仔细一点~
如果说反射面只是单纯的水平或垂直面,那麽反射的运算其实就很简单,就是把垂直於反射面的向量分量倒转而已。
但是如果今天反射面是倾斜的,那麽情况可就大不同了。
下图中描绘着一个即将碰撞到倾斜面的球。(坐标系是使用Canvas的坐标系)
从上面这张图,我们可以先定义两个有关该球碰撞倾斜面当下瞬间的假设,也就是所谓的『碰撞侦测(Collision Detection)』
接着我们再来看看这张图。
假设我们一开始就知道球心的位置,和端点A&B的座标,这样我们就可以藉由这些数据去取得球心到斜面的垂直距离。
算法大致上是这样的:
// 已知球心座标和两端点座标
// 所以这样也就可以取得『向量AP』和『向量BP』还有反射面端点A到B形成的『向量AB』
let vectorAPP = vectorAP.project(vectorAB); //透过我们之前写的向量类中的投射(project)方法来取得『向量APP』
let dist = vectorAP.substract(vectorAPP).length(); // 透过向量差值来取得d的长度
如果对高中数学还有印象的人其实也可以用点到线公式去求取距离,不过那就会是另外一套算法了
接下来我们要讲讲所谓的『帧间误差
』和『归位(reposition)
』。
我们在前面有提到过,canvas的动画是藉由不断地清除画面後再重绘来达成的,所以说事实上球的动画并不是一个完全连续的运动。
意思就是说假设帧率为60
,1/60
秒的时候球的位置在0
, 而2/60
秒的时候球的位置在0.1
,但是实际上1/60~2/60
秒之间,球是没有在运动的。
球的运动其实是『格进』,也就是说球在接近反射面的过程中,非常可能不会刚好有『球心与反射面距离 = 半径』的状况,多半情况下球可能会『插』进去了反射面里面。
这个就是所谓的『帧间误差
』,而应对帧间误差的方式之一就是使用『归位(reposition)
』:
假设球已经『插』进去反射面,则把它归位回正好『球心与反射面距离 = 球半径』的位置。
归位後的位置,我们可以透过球心和反射面距离d,利用向量计算和比例原理去计算出来,就像下面这样:
图片来自 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)
』後的座标,那也就意味着我们可以计算出来归位前一帧到归位时所移动的长度,这样也就可以进一步计算出归位时的速度。
最後我们来到反射
角度的计算,这部分就相对简单一些。
反射
後的速度向量,我们在这边可以把他分割成沿着反射面方向
的部分,和垂直於反射面
的部分,而计算上则可以直接透过向量类
的project方法来求得垂直於反射面
的部分,最後再用扣的去取得沿着反射面方向
的部分。
normalVelocity = dist.para(ball.velocity.projection(dist),-1);
tangentVelocity = ball.velocity.subtract(normalVelocity);
最後再把这两个向量用向量类
的add方法合并起来就好~
这边我们讲解完了反射
的运算方法,在接下来的部分我们会继续带到斜向抛射
的部分,然後在逐渐地去把我们在文章中描述的逻辑构建到程序中~
上一篇我们学到怎麽使用Vim,还有修改commit message,这次要做的事情呢,就是要来合并跟...
在专案里,所有的档案都预先被 import 在一起的,可直接呼叫其他 gs档里的变数与函式。gs档...
在android开发,很常会用到网络资料请求的套件,retrofit是一种 HTTP 请求的工具,使...
我自己是个原生语言的开发者,所以选择 Apple 官方主推的 Swift 语言。开发工具也选 App...
原本小弟我在WinCE上使用SqlCeEngine都挺好用的 奈何最近专案需要使用PC 於是我查了一...