Перевод статьи 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?
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.