
Перевод статьи Emanuele Feronato "Симуляция радиальной гравитации..."

Что-то давно в блогах не было статей в которых бы описывалась реализация чего-нибудь интересного связанного с физикой. Поэтому я решил добавить сюда свой перевод статьи Emanuele Feronato Simulate radial gravity (also known as “planet gravity”) with Box2D as seen on Angry Birds Space
Я уверен, что после выхода Angry Birds Space, многие из вас заинтересовались как самому создать планетарную гравитацию при помощи Box2D.
И знаете что? Это довольно-таки просто.
Начнем.
Во-первых, в космосе нету гравитации, так что мы создаем мир без нее:
private var world:b2World=new b2World(new b2Vec2(0,0),true);
Теперь остается только прикладывать к телам силу относительно их положения и положения планет:
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import Box2D.Dynamics.*;
import Box2D.Collision.*;
import Box2D.Collision.Shapes.*;
import Box2D.Common.Math.*;
import Box2D.Dynamics.Joints.*;
public class Main extends Sprite {
private var world:b2World=new b2World(new b2Vec2(0,0),true);
private var worldScale:Number=30;
private var planetVector:Vector.<b2Body>=new Vector.<b2Body>();
private var debrisVector:Vector.<b2Body>=new Vector.<b2Body>();
private var orbitCanvas:Sprite=new Sprite();
public function Main() {
addChild(orbitCanvas);
orbitCanvas.graphics.lineStyle(1,0xff0000);
debugDraw();
addPlanet(180,240,90);
addPlanet(480,120,45);
addEventListener(Event.ENTER_FRAME,update);
stage.addEventListener(MouseEvent.CLICK,createDebris);
}
private function createDebris(e:MouseEvent):void {
addBox(mouseX,mouseY,20,20);
}
private function addPlanet(pX:Number,pY:Number,r:Number):void {
var fixtureDef:b2FixtureDef = new b2FixtureDef();
fixtureDef.restitution=0;
fixtureDef.density=1;
var circleShape:b2CircleShape=new b2CircleShape(r/worldScale);
fixtureDef.shape=circleShape;
var bodyDef:b2BodyDef=new b2BodyDef();
bodyDef.userData=new Sprite();
bodyDef.position.Set(pX/worldScale,pY/worldScale);
var thePlanet:b2Body=world.CreateBody(bodyDef);
planetVector.push(thePlanet);
thePlanet.CreateFixture(fixtureDef);
orbitCanvas.graphics.drawCircle(pX,pY,r*3);
}
private function addBox(pX:Number,pY:Number,w:Number,h:Number):void {
var polygonShape:b2PolygonShape = new b2PolygonShape();
polygonShape.SetAsBox(w/worldScale/2,h/worldScale/2);
var fixtureDef:b2FixtureDef = new b2FixtureDef();
fixtureDef.density=1;
fixtureDef.friction=1;
fixtureDef.restitution=0;
fixtureDef.shape=polygonShape;
var bodyDef:b2BodyDef = new b2BodyDef();
bodyDef.type=b2Body.b2_dynamicBody;
bodyDef.position.Set(pX/worldScale,pY/worldScale);
var box:b2Body=world.CreateBody(bodyDef);
debrisVector.push(box);
box.CreateFixture(fixtureDef);
}
private function debugDraw():void {
var debugDraw:b2DebugDraw = new b2DebugDraw();
var debugSprite:Sprite = new Sprite();
addChild(debugSprite);
debugDraw.SetSprite(debugSprite);
debugDraw.SetDrawScale(worldScale);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit);
debugDraw.SetFillAlpha(0.5);
world.SetDebugDraw(debugDraw);
}
private function update(e:Event):void {
world.Step(1/30, 10, 10);
world.ClearForces();
for (var i:int=0; i<debrisVector.length; i++) {
var debrisPosition:b2Vec2=debrisVector[i].GetWorldCenter();
for (var j:int=0; j<planetVector.length; j++) {
var planetShape:b2CircleShape=planetVector[j].GetFixtureList().GetShape() as b2CircleShape;
var planetRadius:Number=planetShape.GetRadius();
var planetPosition:b2Vec2=planetVector[j].GetWorldCenter();
var planetDistance:b2Vec2=new b2Vec2(0,0);
planetDistance.Add(debrisPosition);
planetDistance.Subtract(planetPosition);
var finalDistance:Number=planetDistance.Length();
if (finalDistance<=planetRadius*3) {
planetDistance.NegativeSelf();
var vecSum:Number=Math.abs(planetDistance.x)+Math.abs(planetDistance.y);
planetDistance.Multiply((1/vecSum)*planetRadius/finalDistance);
debrisVector[i].ApplyForce(planetDistance,debrisVector[i].GetWorldCenter());
}
}
}
world.DrawDebugData();
}
}
}
Большая часть этого кода это просто создание статических тел (planet), а также динамических (debris), по щелчку мышки.
Самое интересное происходит в функции Update, в цикле for, именно его мы и разберем строчка за строчкой:
for (var i:int=0; i<debrisVector.length; i++) {
Цикл который находит все debri хранящиеся в debrisVector, векторе который создается в строке 14 и обновляется при добавлении новых тел, в строке 54.
var debrisPosition:b2Vec2=debrisVector[i].GetWorldCenter();
Узнаем позицию тел.
for (var j:int=0; j<planetVector.length; j++) {
Цикл который находит все planet хранящиеся в planetVector, векторе который создается в строке 13 и обновляется при добавлении новых тел, в строке 38.
var planetShape:b2CircleShape=planetVector[j].GetFixtureList().GetShape() as b2CircleShape;
Нам надо узнать массу планеты, поскольку чем больше планета, тем сильнее она притягивает к себе тела. К сожалению в Box2D статические тела не имеют массу, поэтому мы берем планету…
var planetRadius:Number=planetShape.GetRadius();
… и узнаем ее радиус. Так-что в нашем случае, чем больше радиус, тем сильнее ее притяжение.
var planetPosition:b2Vec2=planetVector[j].GetWorldCenter();
Узнаем позицию планеты
var planetDistance:b2Vec2=new b2Vec2(0,0);
Создаем новую переменную типа b2Vec2, в которой будем хранить расстояние между planet и debri.
planetDistance.Add(debrisPosition);
Добавляем координаты debri и…
planetDistance.Subtract(planetPosition);
… вычитаем координаты планеты.
var finalDistance:Number=planetDistance.Length();
находим расстояние между planet и debri
if (finalDistance<=planetRadius*3) {
Проверяем, должна ли воздействовать гравитация на тело (в нашем случае, debri должен находится в радиусе равном радиусу планеты умноженом на три)
planetDistance.NegativeSelf();
Инвертируем расстояние до планеты так, чтобы эта сила двигала debri к центру planet.
var vecSum:Number=Math.abs(planetDistance.x)+Math.abs(planetDistance.y);
Узнаем сумму компонентов вектора расстояния. Это нужно чтобы сделать гравитационное притяжение слабее, если debri находится далеко от planet и наоборот.
planetDistance.Multiply((1/vecSum)*planetRadius/finalDistance);
Это финальная формула, благодаря которой притяжение будет динамическим и меняться в зависимости от расстояния.
debrisVector[i].ApplyForce(planetDistance,debrisVector[i].GetWorldCenter());
Ну и наконец-то, прикладываем силу у телу.
А вот и результат:
- +14
- ggman
Комментарии (10)
Модуль силы по какими-то шаманским формулам вычисляется.
Есть же конкретная формула, остается только подставить значения:
Зачем считать вектор в обратную сторону и затем его разворачивать, если его сразу можно вычислить в нужном направлении.
З.Ы. В 2.1a уже есть контроллер радиальной гравитации, туда достаточно добавить одну проверку, чтобы добиться аналогичного эффекта.
Почему здесь не сумма квадратов?
Неужели действительно это формула используется в Angry Birds?