1.质点运动学和Stun2DPhysics引擎
这篇文章将介绍运动学的基本规律,包括质点的位移、速度和加速度之间的关系和计算机程序的实现,也是后面的质点动力学的基础。
物理知识
质点是一个理想化物理模型,无需考虑它的大小形状,在高中物理中大多数情况中都可以把物体看成一个有质量的点,而且点也没有旋转的概念。运动学是对物体运动的研究,并不考虑物体上的作用力,我们只关心质点的位移、速度和加速度随时间变化的规律。
在初中物理中我们就已经学过匀速直线运动,加速度为0,位移与速度关系为:
st=s0+vt
式中st表示t秒末的位移,s0表示初始位移,v为速度,它是一个有方向的矢量,表征物体运动的快慢,从另一个角度理解,它也是物体单位时间中位移的改变量,例如v=5m/s向右,即表示物体每秒钟向右移动5m的距离。
到了高中,我们又学习了匀变速直线运动,此运动中速度与加速度的关系为:
vt=v0+at
加速度a也是矢量,表示速度变化的快慢,从另一个角度理解,它也是物体单位时间中速度的改变量,例如a=5m/s2向右,表示物体的速度每秒钟在向右方向增加5m/s。 而位移、速度和加速度之间的关系为:
st=s0+v0t+at2/2
以上公式只能处理a不变的情况,如果a也会变化,那么必须要借助高等数学中微积分的知识才能解决。简单地说,速度就是位移对时间的一阶导数,加速度就是速度对时间的一阶导数,或者说加速度是位移对时间的二阶导数,可以用以下公式表示:
v=sʹ,a=vʹ,a=s˝
但在计算机实现上,我们更关心的是已知加速度如何求速度,如果知道速度求位移,这时就要用到微分的逆运算—积分,因为a=vʹ,所以逆向分析,即速度是加速度对时间的累积,写成公式就是:
v=∫ adt
同样的道理位移是速度对时间的累积,写成公式就是:
s=∫ vdt
式中的dt指一个无穷小的时间间隔,这在数学上是可行的,但在计算机上不可能存在无穷小的时间间隔,但只要时间间隔取得足够小,积分运算误差就不会很大,这倒印证了计算机图形学上的一句名言:如果看上去是对的,那它就是对的。通常帧频为30帧/秒,时间间隔为1/30秒,这已经够小了,而且为了减少系统负担,在进行物理模拟运算时这个时间间隔还会取得大一些。
因此在计算机上要实现v=∫ adt,s=∫ vdt的伪代码如下:
void Update(dt) { v+=a*dt; s+=v*dt; }
以上内容只讨论了一维方向上的运动,如果扩展到二维一点也不难,只需在两个维度上进行同样的操作,即:
vx=∫ axdt,vy=∫ aydt
sx=∫ vxdt,sy=∫ vydt
这在物理学上叫做运动独立性原理,即:一个物体同时参与几种运动,各分运动都可看成独立进行的,互不影响,物体的合运动则视为几个相互独立分运动叠加的结果。分运动和合运动之间具有:独立性、等时性、矢量性、同体性。高中阶段如果你选修物理就会学到这个知识。
三维空间中的情况也是一样的。
有以上公式我们可以知道,物体的运动情况归根结底是由它的初始状态(初始位移和初速度)和加速度(严格地说,应该是由受力情况决定的,这会在后面的质点动力学中进行讨论)决定的,因此只需设置物体的初始位置、初速度和加速度,计算机程序就会自动算出任意时刻物体的位置了。
实现Stun2DPhysics
理解了背后的物理知识,下面我们就开始实现一个最简陋的2D物理引擎,我把它命名为Stun2DPhysics。主要参考了Farseer Physics Engine2.13,虽然截止到2011年5月26日,Farseer Physics Engine的版本已经更新到了3.3.1,但是因为3.0版本相对于2.0版本变化较大,而且我也不太看的懂,所以Stun2Dphysics的第一个版本还是以2.0版本为蓝本的。
首先创建一个名为Stun2Dphysics的新项目,项目类型为Windows Game Library。然后在项目中添加名称为Dynamics文件夹,在这个文件夹中添加一个名为Body的类,代码如下:
using Microsoft.Xna.Framework; namespace Stun2DPhysics.Dynamics { ////// Body处理物理属性: 位置、速度、加速度等... /// public sealed class Body { ////// 上一次更新时的线速度 /// private Vector2 previousLinearVelocity = Vector2.Zero; ////// 上一次更新时的位置 /// private Vector2 previousPosition = Vector2.Zero; ////// Body的当前位置 /// internal Vector2 position = Vector2.Zero; ////// Body是否静止。 /// internal bool isStatic; ////// 获取或设置线速度。 /// public Vector2 LinearVelocity = Vector2.Zero; ////// 获取或设置加速度。 /// public Vector2 Acceleration = Vector2.Zero ; ////// 创建一个Body对象 /// public Body() {} ////// 获取或设置Body的位置。 /// public Vector2 Position { get { return position; } set { position = value;} } ////// 表示Body是否是固定的。 /// public bool IsStatic { get { return isStatic; } set { isStatic = value; } } #region 积分运算临时变量 private Vector2 ds; private Vector2 dv = Vector2.Zero; #endregion ///地面和边界通常就是固定的。 ////// 由引擎在内部调用,对速度进行积分运算。 /// /// 时间间隔。 internal void IntegrateVelocity(float dt) { // 速度的该变量即加速度与时间的乘积 dv.X = Acceleration.X * dt; dv.Y = Acceleration.Y * dt; // 保存上一次更新时的速度 previousLinearVelocity = LinearVelocity; // 计算当前速度 LinearVelocity.X = previousLinearVelocity.X + dv.X; LinearVelocity.Y = previousLinearVelocity.Y + dv.Y; } ////// 由引擎在内部调用,对位置进行积分运算。 /// /// 时间间隔 internal void IntegratePosition(float dt) { // 位置的改变量即速度乘以时间 ds.X = LinearVelocity.X * dt; ds.Y = LinearVelocity.Y * dt; if (ds != Vector2.Zero) { // 保存上一次更新时的位置 previousPosition = position; // 计算当前位置 position.X = previousPosition.X + ds.X; position.Y = previousPosition.Y + ds.Y; } } } }
Body表示一个物体,目前表示一个质点,在很多其他程序中把处理质点的类命名为Particle,但是以后这个类还要处理刚体,因此没有把它命名为Particle。
这个类其实很简单,保存了物体的位置、速度和加速度,而速度和加速度可以直接进行设置。
最关键的代码位于积分运算的IntegrateVelocity()和IntegratePosition()中,不是很难。其实可以写成更简单的代码:
internal void IntegrateVelocity(float dt) { LinearVelocity.X+=Acceleration.X * dt; LinearVelocity.Y+=Acceleration.Y * dt; } internal void IntegratePosition(float dt) { position.X+=LinearVelocity.X * dt; position.Y+=LinearVelocity.Y * dt; }
还可以更简单:
internal void IntegrateVelocity(float dt) { LinearVelocity +=Acceleration * dt; } internal void IntegratePosition(float dt) { position+=LinearVelocity * dt; }
LinearVelocity +=Acceleration * dt就是相当于LinearVelocity.X+=Acceleration.X * dt和LinearVelocity.Y+=Acceleration.Y * dt两行代码的组合,它调用了Vector2的+=方法,需要额外的函数调用,对于对速度比较敏感的物理引擎来说,还是分别写了两行代码比较快一些,这就相当于C++中内联函数。
但在以后的应用中还需用到上一次更新的速度和位置,所以代码中进行了额外的工作。有了Body类,下面需要创建一个新的类管理它,它就是引擎类PhysicsSimulator,代码如下:
using System; using System.Collections.Generic; using Stun2DPhysics.Dynamics; namespace Stun2DPhysics { public class PhysicsSimulator { ////// 保存所有Body的集合 /// private List bodyAddList; ////// 如果设置为false,物理系统会停止工作。 /// public bool Enabled = true; ////// 创建一个 public PhysicsSimulator() { ConstructPhysicsSimulator(); } private void ConstructPhysicsSimulator() { bodyAddList = new List(32); } ///类对象。 /// /// 在集合中添加一个Body /// /// public void Add(Body body) { if (body == null) throw new ArgumentNullException("body","body不能为null"); if (!bodyAddList.Contains(body)) { bodyAddList.Add(body); } } ///// 更新物理系统。 /// /// 时间间隔。 public void Update(float dt) { // 如果时间间隔为0或者物理系统没有激活则不进行任何操作。 if (dt == 0 || !Enabled) return; UpdatePositions(dt); } ////// 更新所有Body的位置。 /// /// 时间间隔。 private void UpdatePositions(float dt) { for (int i = 0; i < bodyAddList.Count; i++) { if (bodyAddList[i].isStatic) continue; bodyAddList[i].IntegrateVelocity(dt); bodyAddList[i].IntegratePosition(dt); } } } }
这个类管理一个Body的集合,在它的Update方法中更新集合中所有Body的速度和位移。
好了,至此,史上最简陋的物理引擎制作完毕,下一篇文章我们将讨论如何使用这个引擎。
文件下载(已下载 1121 次)发布时间:2011/5/26 上午11:02:44 阅读次数:6326