原文链接: http://www.iforce2d.net/b2dtut/explosions
这篇是个有趣的主题。
Explosion:
这篇文章会找一些方法去实现box2d的爆炸效果,这个主题覆盖很多以前我们学过的东西,所以我们先不细看代码部分,先看实现的思想。现在你的脑袋里面充满了box2d的东西,你要戴上耳机来集中精力。
为了模拟爆炸效果,我们必须找到有哪些刚体在爆炸范围内,然后给它们一个冲量,让它们远离爆炸中心。
下面三个方法都可以模拟爆炸效果,但是难易是不同的:
1.用距离的方法来找到爆炸区域内的刚体
2.用射线的方法找到视野内的刚体
3.从爆炸中心点发射很多微小的刚体粒子
实际上最后一种方法我们不用找到爆炸范围内的刚体,因为物理引擎已经为了我们做了,让我们来看看上面三种方法的细节,而且用下面这篇图片来显示三种方法的供给
Applying a blast impulse
在这段里面我不会讲太多的距离跟爆炸力大小的公式,但是我们知道的是爆炸范围的大小会逐渐增大的,
爆炸产生一个强气流向外扩散,直到它跟普遍大气压相等。
总之,我们可以产生下面的公式:
void applyBlastImpulse(b2Body* body, b2Vec2 blastCenter, b2Vec2 applyPoint, float blastPower) { b2Vec2 blastDir = applyPoint - blastCenter; float distance = blastDir.Normalize(); //ignore bodies exactly at the blast point - blast direction is undefined if ( distance == 0 ) return; float invDistance = 1 / distance; float impulseMag = blastPower * invDistance * invDistance; body->ApplyLinearImpulse( impulseMag * blastDir, applyPoint ); }
当然上面的代码也不是完全版本,因为有很多其他条件需要考虑,比如限制最大的爆炸冲量。
Proximity method
最简单的实现方法是以爆炸点为中心,以一个给定的半径来找出在这个圆中所有的半径。我们可以用area query的方法去高效地找到所有的被包含的刚体。
//find all bodies with fixtures in blast radius AABB MyQueryCallback queryCallback; //see "World querying topic" b2AABB aabb; aabb.lowerBound = center - b2Vec2( blastRadius, blastRadius ); aabb.upperBound = center + b2Vec2( blastRadius, blastRadius ); m_world->QueryAABB( &queryCallback, aabb ); //check which of these bodies have their center of mass within the blast radius for (int i = 0; i < queryCallback.foundBodies.size(); i++) { b2Body* body = queryCallback.foundBodies[i]; b2Vec2 bodyCom = body->GetWorldCenter(); //ignore bodies outside the blast range if ( (bodyCom - center).Length() >= m_blastRadius ) continue; applyBlastImpulse(body, center, bodyCom, blastPower ); }
下面让我们来看看结果,下面是个截图,显示哪些刚体需要被施加一个冲量。
这是个相当好的开始,但是还是有很多问题的,最明显的问题是爆炸会穿透障碍物,另一个问题是左边最大的物体在这个爆炸区域内,但是它的质心并不在爆炸区域内:
两边的物体有着相同的质量,并且表示相同的受爆炸的区域,但是右边会受到四倍的爆炸力量,还有下面的情况:
这样的爆炸只会产生推力,却不会产生扭矩,这实在看起来有点奇怪,虽然这种办法有种种缺点,但是很多不是有特殊要求的情况下,还算是不错的解决方案。
Raycast method
我们可以用现在这种方法来改善上个方法的不足,用射线来找到与爆炸区域有相交的刚体,你可以去ray casting跟world querying主题看更多的细节。
for (int i = 0; i < numRays; i++) { float angle = (i / (float)numRays) * 360 * DEGTORAD; b2Vec2 rayDir( sinf(angle), cosf(angle) ); b2Vec2 rayEnd = center + blastRadius * rayDir; //check what this ray hits RayCastClosestCallback callback;//basic callback to record body and hit point m_world->RayCast(&callback, center, rayEnd); if ( callback.m_body ) applyBlastImpulse(callback.body, center, callback.point, (m_blastPower / (float)numRays)); }
需要注意的是:我们分割爆炸来产生一定数据的射线,这样会更容易实现,我们先用32根射线来模拟:
相当不错,现在没有别扭的爆炸力了,所以现在爆炸不会穿透阻碍物了,第二种情况也得到了很好的改善,因为每个边的表面区域都计算进来了。
最后,冲量施加在射线跟夹具的交点上,而这些交点又不是对称的点,所以这样会产生扭矩,从而会旋转物体而产生更有趣的效果
而射线的个数你可以做一个平衡的处理,因为太多会影响cpu的使用,而太少也会忽略一些细小的物体