第2章 物理实验
实验8 质点运动与反射
简介
物理实验属于物理引擎的范畴,物理引擎是计算机图像学与物理学之间的一座桥梁。物理引擎通过为刚性物体赋予真实物理属性的方式来计算运动、旋转和碰撞反应。
物理引擎使用对象属性(动量、扭矩或者弹性)来模拟刚体行为,这不仅可以得到更加真实的结果,对于开发人员来说也比编写行为脚本要更加容易掌握。好的物理引擎允许有复杂的机械装置,像球形关节、轮子、汽缸或者铰链。有些也支持非刚性体的物理属性,如流体。物理引擎可以从其他厂商购买,而一些游戏开发系统具备完整的物理引擎。还要注意,虽然有的系统在其特性列表中说它们有物理引擎,但其实是一些简单的加速和碰撞检测属性而已。物理引擎可以实现粒子效果、流体效果、软体效果等,本实验先从最简单的匀速直线运动谈起。
匀速直线运动
物体在一条直线上运动,且在任意相等的时间间隔内位移相等,这种运动称为匀速直线运动(Uniform Rectilinear Motion),如图2-1所示。位移是速度对时间的累积,写成公式就是:s=∫vdt。而计算机无法模拟无穷小的时间间隔,但是只要时间间隔足够小,积分运算误差就不大。时间间隔为(1/60)s,也就是60帧/秒。
图2-1 匀速直线运动
在实验2台球中,按照如下方式定义小球:
var ball={ position: { x: 100, y: 100 }, r: 15 };
其中包含了小球的位置和半径两个属性,因为要用小球来做物理实验,所以为小球增加两个属性vx和vy,分别代表小球沿X轴方向和沿Y轴方向上的速度。这在物理学上叫做运动独立性原理,即:一个物体同时参与几种运动,各分运动都可看成独立进行的,互不影响,物体的合运动则视为几个相互独立分运动叠加的结果。分运动和合运动之间具有:独立性、等时性、矢量性、同体性。如下所示:
var ball={ position: { x: 100, y: 100 }, r: 15, vx: 190, vy: 110 };
代码中假定了小球沿X轴方向的速度为190,沿Y轴方向上的速度为110。
定义好了小球的速度,如何把它的运动状态体现在Canvas中呢?在实验3中,已经用到了Jscex来实现画圆动画。这里继续使用Jscex来实现小球的运动。
在使用Jscex之前依然要先引用Jscex压缩后的库函数:
<scriptsrc="jscex.min.js"type="text/javascript"></script>
这样就可以使用Jscex了。
<canvasid="myCanvas"width="600"height="500">Your browser does not support the canvas element. </canvas> <scripttype="text/javascript"> var canvas=document.getElementById("myCanvas"); var cxt=canvas.getContext("2d"); var ball={ position: { x: 100, y: 100 }, r: 15, vx: 190, vy: 110 }; var cyc=10; var moveAsync=eval(Jscex.compile("async", function () { while (true) { cxt.fillStyle="rgba(0, 0, 0, .3)"; \注:实现了残影效果。\ cxt.fillRect(0, 0, canvas.width, canvas.height); cxt.fillStyle="#fff"; cxt.beginPath(); cxt.arc(ball.position.x, ball.position.y, ball.r, 0, Math.PI * 2, true); cxt.closePath(); cxt.fill(); ball.position.x +=ball.vx * cyc / 1000; ball.position.y +=ball.vy * cyc / 1000; \注:小球的x 坐标和 y坐标每隔一个周期(cyc)就发生变化。\ $await(Jscex.Async.sleep(cyc)); } })) moveAsync().start(); </script>
也可以让小球往返不停地运动:
var moveAsync=eval(Jscex.compile("async", function () { while (true) { while (ball.position.x < 500) { cxt.fillStyle="rgba(0, 0, 0, .3)"; cxt.fillRect(0, 0, canvas.width, canvas.height); cxt.fillStyle="#fff"; cxt.beginPath(); cxt.arc(ball.position.x, ball.position.y, ball.r, 0, Math.PI * 2, true); cxt.closePath(); cxt.fill(); ball.position.x +=ball.vx * cyc / 1000; ball.position.y +=ball.vy * cyc / 1000; $await(Jscex.Async.sleep(cyc)); } while (ball.position.x > 100) { cxt.fillStyle="rgba(0, 0, 0, .3)"; cxt.fillRect(0, 0, canvas.width, canvas.height); cxt.fillStyle="#fff"; cxt.beginPath(); cxt.arc(ball.position.x, ball.position.y, 15, 0, Math.PI * 2, true); cxt.closePath(); cxt.fill(); ball.position.x -=ball.vx * cyc / 1000; ball.position.y -=ball.vy * cyc / 1000; $await(Jscex.Async.sleep(cyc)); } } })) moveAsync().start();
可以看到上面这段代码共有3个while,从上到下依次无限循环执行下去,效果如图2-2所示。
图2-2 Canvas中模拟匀速运动的小球
倘若小球和四壁碰撞,就会发生反弹,反弹符合反射定律,如图2-3所示。
图2-3 反射定律
其中,入射角i与反射角r是相等的。所以在Canvas中模拟的结果就是:当小球与上下壁相撞时,Y轴速度方向变成其反方向,大小不变,X轴速度不变;当小球与左右两壁碰撞时,X轴速度方向变成其反方向,大小不变,Y轴速度大小及方向都不变。可以得出:
var canvas=document.getElementById("myCanvas"); var cxt=canvas.getContext("2d"); var ball={ x: 100, y: 100, r: 15, vx: 190, vy: 110 }; var cyc=10; var moveAsync=eval(Jscex.compile("async", function () { while (true) { cxt.fillStyle="rgba(0, 0, 0, .3)"; cxt.fillRect(0, 0, canvas.width, canvas.height); cxt.fillStyle="#fff"; cxt.beginPath(); cxt.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2, true); cxt.closePath(); cxt.fill(); //与左右两壁碰撞。 if (ball.r+ball.x > canvas.width || ball.x < ball.r) ball.vx *=-1; //与上下两壁碰撞。 if (ball.r+ball.y > canvas.height || ball.y < ball.r) ball.vy *=-1; ball.x +=ball.vx * cyc / 1000; ball.y +=ball.vy * cyc / 1000; $await(Jscex.Async.sleep(cyc)); } })) moveAsync().start();
这样小球就永远逃不出Canvas画布的范围了,当然这是碰撞的一种特殊情况,因为碰撞面分别与X轴和Y轴平行,所以处理起来非常方便。如果遇到任意碰撞面,就不能这么简单地把方向设反,而要通过向量计算速度的变化,在后面的实验中将解决这一问题。