5.质点动力学——动量定理

上一篇文章我们讨论了牛顿第二定律,理论上根据牛二定律和微积分的知识就可以求出大多数情况下物体的运动情况,但是上一篇文章中实现的物理引擎无法模拟这样一个问题:物体受到一个冲击力的作用而获得初速度,这就是这篇文章将要讨论的问题。

物理知识

首先看这样一道习题:光滑水平桌面上一个质量为1kg的物体初始速度为1m/s,受到初速方向2N的水平推力运动了2s,求2s末物体的速度。

解:根据牛顿定律:a=F/m=2m/s2

根据运动学公式:vt=v0+at=5m/s

非常简单的一道题。如果将以上两式联立,得到的方程为:

vt-v0=Ft/m

整理成

Ft=mvt-mv0

代入数据也能得到相同的结果。如果我们将Ft定义为一个新的物理量:冲量I,mv定义为一个新的物理量:动量p,那么就获得了一个新的物理规律:

ΣI=Δp

这个规律称为动量定理,即:物体所受合力的冲量等于物体的动量变化量

根据动量定理,力F还可以表示为动量的变化率,即:

F=dp/dt

根据这个定律,若物体受到一个冲击力作用时,我们往往无法知道这个冲击力的大小和作用时间,但是可以直接设置一个冲量,就可以求得物体的速度并进一步求出位移,公式如下:

vtv0I/m

传统的力学教材是以牛顿运动三定律为核心来展开的,并把质量和力作为动力学中最基本的概念,从而导出动量和能量的概念以及其他守恒定律。然而从现代物理的高度来看,在描述物质的运动和相互作用时,动量和能量的概念要比力的概念基本得多。我看过的《新概念物理教程力学》就是这样编排的,先介绍动量,然后从动量推出牛顿定律。2008年上海市曾向物理教师推荐过一套德国高中物理教材《卡尔斯鲁厄物理课程》也是这个思路,与我国传统的高中教材有很大不同,好像我区的大同中学还进行了试点教学。

卡尔斯鲁厄物理课程

在《GAME PHYSICS ENGINE DEVELOPMENT》一书的第一章中作者也提到:大多数物理引擎是以力为基础创建(force-based engine)的,将动量看成短时间内作用的力,缺点是处理力要比处理动量难;还有些物理引擎基于动量(impulse-based engine),缺点是当帧频不够时,原来静止的物体会发生移动;极少数物理引擎使用力处理静止接触,使用动量处理碰撞。

在Stun2DPhysics中的实现

非常简单,只需在Body类中添加一个方法即可:

/// <summary>
/// 施加线性冲量。 
/// </summary>
/// <param name="impulse" />冲量</param>
public void ApplyLinearImpulse(Vector2 impulse)
{
    if (isStatic)
        return;
        
    dv.X = impulse.X * inverseMass;
    dv.Y = impulse.Y * inverseMass;
    
    LinearVelocity.X = dv.X + LinearVelocity.X;
    LinearVelocity.Y = dv.Y + LinearVelocity.Y;
}

这个方法施加的是线性冲量,在以后讲到刚体动力学时还会添加角冲量。

Silverlight示例

接下来,我们就创建一个silverlight程序演示新代码的用法。 首先为了让代码更清晰,我创建了一个Sprite.cs文件,代码如下:

namespace Stun2DPhysics4SLDemo
{
    public abstract class Sprite
    {
        // 与Sprite链接的UserControl对象
        protected UserControl ucSpriteUC;
        
        // 父Canvas控件
        protected Canvas cnvParent;
        
        // 获取或设置UseControl的位置
        public Point Location { get;set;}

        public Sprite(Canvas setCnvParent, Point initialLocation)
        {
            cnvParent = setCnvParent;
            ucSpriteUC = CreateSpriteUC();
            Location = initialLocation;

            cnvParent.Children.Add(ucSpriteUC);
            
            // 设置UseControl的初始位置
            ucSpriteUC.SetValue(Canvas.LeftProperty, Location.X);
            ucSpriteUC.SetValue(Canvas.TopProperty, Location.Y);
        }

        public abstract UserControl CreateSpriteUC();

        public virtual void Update()
        {            
        }
    }

    public abstract class SpritePhysice : Sprite
    {
        public Body Body { get; private set; }

        public SpritePhysice(Canvas cnvParent, Point initialLocation, PhysicsSimulator physicsSimulator)
            : base(cnvParent, initialLocation)
        {
            Body = new Body();
            Body.Position = new Vector2((float)initialLocation.X, (float)initialLocation.Y);
            physicsSimulator.Add(Body);
        }

        public override void Update()
        {
            ucSpriteUC.SetValue(Canvas.LeftProperty, Convert.ToDouble(Body.Position.X));
            ucSpriteUC.SetValue(Canvas.TopProperty, Convert.ToDouble(Body.Position.Y));
        }
    }

    public class Box1Sprite : SpritePhysice
    {
        public Box1Sprite(Canvas cnvParent, Point initialLocation, PhysicsSimulator physicsSimulator)
            : base(cnvParent, initialLocation,physicsSimulator)
        {
            
        }

        public override UserControl CreateSpriteUC()
        {
            return new Box1();
        }
    }

    public class Box2Sprite : SpritePhysice
    {
        public Box2Sprite(Canvas cnvParent, Point initialLocation, PhysicsSimulator physicsSimulator)
            : base(cnvParent, initialLocation, physicsSimulator)
        {

        }

        public override UserControl CreateSpriteUC()
        {
            return new Box2();
        }
    }
}

此文件包含四个类,首先是抽象基类Sprite,它链接了一个绘制图像的UseControl,派生类可以重写抽象方法CreateSpriteUC()初始化这个UseControl,派生类还可以重写虚拟方法Update()编写更新时的行为。如果Sprite无需物理属性,就可以从这个类继承。 抽象类SpritePhysice类继承自Sprite类,额外多了用于物理计算的Body类,并在Update()方法中用Body的位置控制SpritePhysice中UseControl的位置。 最后两个Box1Sprite和Box2Sprite类就代表在屏幕上出现的两个矩形。

后台MainPage.xaml.cs代码如下:

namespace Stun2DPhysics4SLDemo
{
    public partial class MainPage : UserControl
    {
        // 当一帧绘制结束时保存当前时刻
        private DateTime LastTick;
        // 物理引擎
        PhysicsSimulator physicsSimulator;

        // 保存Sprite的集合
        private List<sprite> sprites;

        Box1Sprite box1;
        Box2Sprite box2;
        
        bool isRunning = false ;

        public MainPage()
        {
            InitializeComponent();

            physicsSimulator = new PhysicsSimulator();
            physicsSimulator.Gravity = new Vector2(0, 150);

            sprites = new List<Sprite>();

            box1 = new Box1Sprite(LayoutRoot, new Point(16, 16), physicsSimulator);
            box1.Body.LinearVelocity = new Vector2(150, 0);

            box2= new Box2Sprite(LayoutRoot, new Point(768, 32), physicsSimulator);
            box2.Body.IgnoreGravity = true;

            sprites.Add(box1);
            sprites.Add(box2);
        }
        
        private void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            // 保存自上一次更新以来流逝的时间
            TimeSpan elapsedTime = (DateTime.Now - LastTick);

            physicsSimulator.Update((float)elapsedTime.TotalSeconds);
            
            // 对box1施加一个水平向右的风力
            const float forceAmount = 50;
            Vector2 force = Vector2.Zero;
            force += new Vector2(forceAmount, 0);
            box1.Body.ApplyForce(force);

            for (int i = 0; i < sprites.Count; i++)
            {
                sprites[i].Update();
            }

            // 处理物体在边界上的碰撞,以后会通过引擎中的碰撞检测实现
            if (box1.Body.Position.X > 784 || box1.Body.Position.X < 16)
                box1.Body.LinearVelocity.X *= -1;
            if (box1.Body.Position.Y > 464 || box1.Body.Position.Y < 16)
                box1.Body.LinearVelocity.Y *= -1;
            if (box2.Body.Position.X > 768 || box2.Body.Position.X < 32)
                box2.Body.LinearVelocity.X *= -1;
            if (box2.Body.Position.Y > 448 || box2.Body.Position.Y < 32)
                box2.Body.LinearVelocity.Y *= -1;

            // 保存当前时刻
            LastTick = DateTime.Now;
        }

        private void btnStart_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            if (!isRunning)
            {
                btnStart.Content = "暂停";
                // 保存当前时刻
                LastTick = DateTime.Now;
                CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
                this.KeyDown += new KeyEventHandler(Page_KeyDown);
                isRunning = true;
            }
            else
            {
                btnStart.Content = "继续";
                CompositionTarget.Rendering -= new EventHandler(CompositionTarget_Rendering);
                this.KeyDown -= new KeyEventHandler(Page_KeyDown);
                isRunning = false;
            }
        }

        private void Page_KeyDown(object sender, KeyEventArgs e)
        {
            // 如果按下A键则对box2施加一个向左的冲量
            if(e.Key==Key.A)
            {
                box2.Body.ApplyLinearImpulse (new Vector2(-100,0));
                box2.Body.IgnoreGravity = false;
            }
        }
    }
}

对左边的矩形施加了一个水平向左的恒力模拟风力,在这个风力的影响下,我们可以明显看到矩形向右移动的水平射程要比向左运动的大。对于右边的矩形,一开始是不动的,按下键盘的A键可以对它施加一个水平向左的冲量使它获得初速度,你可以持续地按下A键看看会发生什么结果。

这个引擎在XNA中的应用我就不写教程了,源代码可在本文的上一级页面中下载,效果和Silverlight是一样的。

文件下载(已下载 1007 次)

发布时间:2011/6/9 上午7:37:34  阅读次数:6189

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号