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 阅读次数:6947
