Перевод статьи 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

Комментарии (10)

+2
Очевидный алгоритм, сам так бы и делал))
0
с крагами легко, в вот как на счёт сложных составных форм?
0
Разбить на круги?
0
я про обьект состоящий из прямоугольников и кругов. Сильно не пытался, но, когда ковырялся, то с наскоку не получилось ничего
0
Гравитация считается между материальными точками — центрами масс взаимодействующих тел, не важно какой оно формы. Центр масс бокс рассчитывает автоматически, или можешь вручную задать. В каком месте проблема? Или просто с помощью «радиальной» гравитации ты пытаешься решить немного другие задачи.
0
Центр масс бокс рассчитывает автоматически
не знал, спасибо
0
Вообще без разницы какой там шейп, его может и не быть вовсе.
+1
К сожалению в Box2D статические тела не имеют массу, поэтому мы берем планету
Начиная с 2.1a — имеют.

Модуль силы по какими-то шаманским формулам вычисляется.
Есть же конкретная формула, остается только подставить значения:


Инвертируем расстояние до планеты
Зачем считать вектор в обратную сторону и затем его разворачивать, если его сразу можно вычислить в нужном направлении.

З.Ы. В 2.1a уже есть контроллер радиальной гравитации, туда достаточно добавить одну проверку, чтобы добиться аналогичного эффекта.
0
Видимо Emanuele Feronato, работает на со старой версией Box2D. А я вообще с Nape II.
0
var vecSum:Number=Math.abs(planetDistance.x)+Math.abs(planetDistance.y);

Почему здесь не сумма квадратов?
Неужели действительно это формула используется в Angry Birds?
  • RoKo0
  • RoKo0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.