写在前面
2011年在写了个物理引擎,期间重新啃起了物理课本,一晃就是5年,
当年自己写的物理引擎的代码又阅读一遍,受益匪浅,加上最近制作坦克争霸使用Box2d的思考,对物理引擎管线又有了新的认识和体会。
人除了造人,还可以是造世界,这两种时候人能够扮演上帝的角色。有人会说:“几个小球撞来撞球算哪门子世界?”引用《黑客帝国》里
男主角的话:“哪一个才是真实的世界?”在小球的眼里,它的世界就是真实的世界,只是小球无意识,意识形态的程序设计太复杂,
如果有一天意识形态能用程序表达并通过图灵测试,那么:"哪一个才是真实的世界?同样都是原子构成的世界,哪一个才是真实的世界?"。
不废话,现在就开始吧...
准备好一款浏览器,而且必须是现代浏览器(如Google Chrome最新版),因为物理引擎虽然支持老的浏览器,但是为了看到这个物理世界发生的一切,会在canvas里渲染刚体。
顶级NameSpace为了纪念牛顿,使用Newton作为顶级命名空间。
var Newton = {};
Class.js代码的分类抽象完全基于Class,使用的class.js如下所示:
.(prop) .prototype (var name in prop) { prototype[name] (() ..ret arguments)tmp)(name, prop[name]) : prop[name]() arguments)...Class.Class; };
向ES6靠齐的class.js,暴露prototype给语言使用者总是不友好的,大概的使用方式如下:
以前写过一篇文章介绍。
Vector2Vector2,一般用来表示向量,有的时候也用来当作点来进行计算。
.(xxy() ..() ....() / invinv(v) ...(f) ff(v) ....(v) ./ (x..);
其中
除了clone方法,其余方法都不会创建新的Vector2,这里不能为了使用的代码可以连缀而创建大量的Vector2。
知识准备[角]速度等于加速度在时间上的累加
v = a*t
[角]位移等于速度在时间上的累加
s = v*t
加速度等于过物体重心的力除以质量(最常见的物体受地球的吸引力,即重力。把物体看成质点,而且过重心先不用考虑角速度)
F = ma
运动的独立性
一个物体同时参与几种运动,各分运动都可看成独立进行的,互不影响,物体的合运动则视为几个相互独立分运动叠加的结果
如下图的运动小球:
可拆分成如下三种运动分量:
牛顿的世界世界里需要模拟时间流逝,去累加速度、位移。
时间是连续的还是非连续的?到底有没有最小时间片?最小时间片是多少?现代物理依然无法给出定论。
但是在物理引擎里,时间是非连续的。
.() []./);
如上面代码所示,bodies为世界里的所有物体,bodiesLen为物体的数量。timeStep为最小时间片段。
时间流逝.extend({ ... ... () ..()(k.(body) ..... ...
世界可以通过add方法向世界增加物体,上面的tick处理世界上发生的所有事件,目前仅仅是调用了物体自身的tick。
ticker代码如下:
(Ticker currTime ()..(currTime .timeToCall); lastTime .());
这里不使用requestAnimationFrame的,用的智能setTimeout。因为tick里面以后会包含很多逻辑,如重力处理、AABB优化、碰撞检测、碰撞处理、重叠处理、休眠处理,
requestAnimationFrame里的函数是在repaint之前调用,和复杂且耗时的程序逻辑混在一起会导致帧率下降,起反作用。
小球,是这个世界第一个物体。它除了不分男女,与生俱来许多属性(运动和碰撞相关的属性)。
.(option) .....)..).(...../(dt) .....(dt) ...(dt) ) ..dt / (dt) .)
上面的构造函数里,会把传入的参数覆盖默认的参数配置,并且提前计算好重力的倒数,因为重力的倒数会被经常用到。
好了。到目前为止已经完成了一款简陋的物理引擎,包含了物理引擎管线的:重力处理。下面要通过canvas把物理引擎的运作过程可视化。
.(selector) ..() ..(x....r.)) .).);
上面使用setTransform设置变换矩阵,能完成rotate(), scale(), translate(), or transform() 所能完成的工作。
使用下面代码测试绘制:
..PI / 180);
可以看到下面的效果:
这里为了能看出旋转的角度,从圆的重心向右边r的位置画了一条线段。因为后续文章当中,也会出现矩形的刚体,同样,我们可以封装一个绘制矩形的
方法:
rect.....h / .)).).
让物理引擎跑起来...).).......rotation); }) world.start();
上面Circle的参数里面:
按照上面一步一步,你将看到一个小球从(100,20 )的位置加速旋转掉飞下。
第一次重构因为Newton.Circle 的大部分属性和方法,在其他的刚体中也适用,只有半径这个东西是Circle特有的,
所以将Newton.Circle改名为Newton.Body,并移除属性r,然后Newton.Circle 的代码就变成了:
.(option) ..);
加四面墙.c1 ..).)........) ...(.) ...(.) ...(.) ...(...() world.start();
现在你可以看到一个小球在画布里,撞来撞去最后静止。
world.onTick里面加了一大堆逻辑,用来处理小球与400*400的Canvas的碰撞,以及角速度和角速度的衰减,位置的矫正(重叠处理),到最后
的静止。因为世界只有圆一种刚体,所有只能先这样实现。但是上面的onTick里新加的代码,其实可以窥见物理引擎管线中的必备流程:
... ... ... c1 ....)..( evt) .....canvas.(evt))..ii.....) { ... ... ...
效果如下:
因为所有的刚体都会被push进world.bodies,所有在onTick中需要遍历所有的小球进行绘制和与墙面的碰撞检测。
最后本篇幅主要做了大量的准备工作包含class.js、ticker.js、vector2.js、render.js,真正的物理引擎的部分只占了小部分,后续的文章的占比会恰好相反。
虽然社区里有许多成熟的物理引擎,但自己实现一款物理引擎有非常多的好处:
未完待续..
下篇预告:《零基础制作物理引擎--创造力量 》