Weld — крепкое соединение. Как правило оно не может растягиваться или сжиматься (расстояние между телами стремится быть постоянным), а так же тела не могут поворачиваться относительно друг друга (угол между телами стремиться быть постоянным). Получается жёсткое фиксирование тел относительно друг друга с соединением их в определённой точке.
Я продемонстрирую несколько основных видов объектов с применением данного соединения:
- прикрепление обычного динамического тела к мировому телу;
- скрепление 2-х тел (квадратов);
- шестерёнка или её подобие;
- упругая антенна — интересный болтающийся объект.
Вот как это выглядит
Контролы: P — скрыть\показать профайлер; R — пересоздать сцену; LMB — таскать объекты мышью.
package { import Box2D.Collision.b2AABB; import Box2D.Collision.Shapes.b2CircleShape; import Box2D.Collision.Shapes.b2PolygonShape; import Box2D.Collision.Shapes.b2Shape; import Box2D.Common.Math.b2Vec2; import Box2D.Dynamics.Joints.b2WeldJointDef; import Box2D.Dynamics.b2Body; import Box2D.Dynamics.b2BodyDef; import Box2D.Dynamics.b2DebugDraw; import Box2D.Dynamics.b2Fixture; import Box2D.Dynamics.b2FixtureDef; import Box2D.Dynamics.b2World; import Box2D.Dynamics.Joints.b2MouseJoint; import Box2D.Dynamics.Joints.b2MouseJointDef; import flash.display.DisplayObjectContainer; import flash.display.Graphics; import flash.display.Sprite; import flash.events.Event; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.ui.Keyboard; import net.hires.debug.Stats; /** * Для статьи "Box2D joints: weld joint" cyber-code.ru * @author Monax */ [Frame(factoryClass="Preloader")] public class Main extends Sprite { private const PPM: Number = 30; // pixel per meter - множитель для перевода метров в пиксели private const MPP: Number = 1 / 30; // meter per pixel - для перевода пикселей в метры private const VEL_ITERS: int = 10; private const POS_ITERS: int = 10; private var _statsParent: Sprite; private var _debugDrawParent: Sprite; private var _world: b2World; private var _mJoint: b2MouseJoint; public function Main():void { if (stage) init() else addEventListener(Event.ADDED_TO_STAGE, init); } ////////////////////////////////////////////////////////////////////////////////////////// //{ PRIVATE private function init(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, init); // создаём слой для отрисовки физики _debugDrawParent = new Sprite(); addChild(_debugDrawParent); // создаём слой для отображения профайлера _statsParent = new Sprite(); addChild(_statsParent); var stats: Stats = new Stats(); _statsParent.addChild(stats); // создаём сцену createScene(); // организуем слушатели событий addEventListener(Event.ENTER_FRAME, onEnterFrameHandler); stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDownHandler); stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); } /** * Создание всей сцены */ private function createScene():void { // создание мира _world = new b2World(new b2Vec2(0, 9.8), true); // инициализация графики initDebugDraw(_debugDrawParent); // создание стен createWalls(true, true, true); // создание объектов createObjs(_world); } /** * Полное уничтожение всей сцены */ private function destroyScene():void { destroyMouseJoint(); // чистим отображение _debugDrawParent.removeChildren(); // уничтожаем все тела в мире if (_world) for (var body: b2Body = _world.GetBodyList(); body; body = body.GetNext()) if (body) _world.DestroyBody(body); // обнуляем переменную мира _world = null; } /** * Инициализация debug-отрисовки */ private function initDebugDraw(aDDParent: DisplayObjectContainer):void { // создаём специальный фон для дебаг отрисовки // это необходимо, чтобы на этапе дебаг отрисовки срабатывали клики мыши например var ddFon: Sprite = new Sprite(); var gr: Graphics = ddFon.graphics; gr.lineStyle(0); gr.beginFill(0x333333, 1); gr.drawRect(-1000, -1000, 2000, 2000); aDDParent.addChild(ddFon); var ddDummy: Sprite = new Sprite(); aDDParent.addChild(ddDummy); var debugDraw: b2DebugDraw = new b2DebugDraw(); debugDraw.SetSprite(ddDummy); debugDraw.SetDrawScale(PPM); // указываем флаги - что необходимо отрисовывать debugDraw.SetFlags(b2DebugDraw.e_controllerBit + b2DebugDraw.e_pairBit + b2DebugDraw.e_shapeBit + b2DebugDraw.e_jointBit); debugDraw.SetFillAlpha(0.5); _world.SetDebugDraw(debugDraw); } /** * Создание стен по размеру видимой области * @param aLeft - создать левую стену? * @param aRight - создать правую стену? * @param aTop - создать потолок? */ private function createWalls(aLeft: Boolean = false, aRight: Boolean = false, aTop: Boolean = false):void { var body: b2Body; var bDef: b2BodyDef; var shape: b2Shape; // создание статического прямоугольника. которые будет в роли земли. создаётся по умолчанию bDef = new b2BodyDef(); shape = b2PolygonShape.AsBox(stage.stageWidth * MPP / 2, 20 * MPP / 2); bDef.position.Set(stage.stageWidth / 2 * MPP, stage.stageHeight * MPP); body = _world.CreateBody(bDef); body.CreateFixture2(shape); // левая стена if (aLeft) { bDef = new b2BodyDef(); shape = b2PolygonShape.AsBox(20 / 2 * MPP, stage.stageHeight / 2 * MPP); bDef.position.Set(0, stage.stageHeight / 2 * MPP); body = _world.CreateBody(bDef); body.CreateFixture2(shape); } // правая стена if (aRight) { bDef = new b2BodyDef(); shape = b2PolygonShape.AsBox(20 * MPP / 2, stage.stageHeight * MPP / 2); bDef.position.Set(stage.stageWidth * MPP, stage.stageHeight * MPP / 2); body = _world.CreateBody(bDef); body.CreateFixture2(shape); } // потолок if (aTop) { bDef = new b2BodyDef(); shape = b2PolygonShape.AsBox(stage.stageWidth / 2 * MPP, 20 / 2 * MPP); bDef.position.Set(stage.stageWidth / 2 * MPP, 0 * MPP); body = _world.CreateBody(bDef); body.CreateFixture2(shape); } } // создание объектов private function createObjs(aWorld: b2World):void { var i: int; var body: b2Body; var bDef: b2BodyDef; var fixDef: b2FixtureDef; /////////////////////////////////////////////////////// // создадим 3 одинаковых прямоугольных объекта (квадраты со стороной в 30 пикселей) // 2 из которых мы соеденим Weld соединением var a: b2Body = createPhysBox(_world, 350, 100, 30, 30); var b: b2Body = createPhysBox(_world, 350, 100, 30, 30); var c: b2Body = createPhysBox(_world, 350, 100, 30, 30); // создадим Weld joint между b и c var jDef: b2WeldJointDef = new b2WeldJointDef(); jDef.bodyA = b; jDef.bodyB = c; // указываем точку соединения, точка для каждого тела указывается в его локальных координатах // например если тело - квадрат со стороной 30, то локальная точка (15,15) - это его правый нижний угол. jDef.localAnchorA = new b2Vec2(-15 * MPP, -15 * MPP); jDef.localAnchorB = new b2Vec2(15 * MPP, 15 * MPP); // можно установить угол соединения, расскоментируйте эту строку и посмотрите что изменится //jDef.referenceAngle = Math.PI / 6; // 30 градусов // создадим соединение в мире _world.CreateJoint(jDef); // теперь соеденим мировое тело с телом a // можно использовать созданный ранее объект jDef для других тел просто переопределяя тела и нужные совйства jDef.bodyA = _world.GetGroundBody(); jDef.bodyB = a; jDef.localAnchorA = new b2Vec2(250 * MPP, 40 * MPP); jDef.localAnchorB = new b2Vec2(); _world.CreateJoint(jDef); /////////////////////////////////////////////////////// // создание шестерёнки: 1 круг и 4 прямоугольника a = createPhysCircle(_world, 100, 100, 60); b = createPhysBox(_world, 100, 100, 30, 30); jDef.bodyA = a; jDef.bodyB = b; // соединяем круг и прямоугольник, соединяются точки края круга и центра прямоугольника jDef.localAnchorA = new b2Vec2(60 * MPP, 0); jDef.localAnchorB = new b2Vec2(0, 0); _world.CreateJoint(jDef); // 2-й прямоугольник шестерёнки b = createPhysBox(_world, 100, 100, 30, 30); jDef.bodyB = b; jDef.localAnchorA = new b2Vec2(0, 60 * MPP); jDef.localAnchorB = new b2Vec2(0, 0); _world.CreateJoint(jDef); // 3-й прямоугольник шестерёнки b = createPhysBox(_world, 100, 100, 30, 30); jDef.bodyB = b; jDef.localAnchorA = new b2Vec2(-60 * MPP, 0); jDef.localAnchorB = new b2Vec2(0, 0); _world.CreateJoint(jDef); // 4-й прямоугольник шестерёнки b = createPhysBox(_world, 100, 100, 30, 30); jDef.bodyB = b; jDef.localAnchorA = new b2Vec2(0, -60 * MPP); jDef.localAnchorB = new b2Vec2(0, 0); _world.CreateJoint(jDef); /////////////////////////////////////////////////////// // создание антенны // 3 первых кусочка a = createPhysBox(_world, 300, 300, 12, 30); b = createPhysBox(_world, 300, 300, 10, 30); c = createPhysBox(_world, 300, 300, 8, 30); // прикрепляем первый кусочек к телу мира jDef.bodyA = _world.GetGroundBody(); jDef.bodyB = a; jDef.localAnchorA = new b2Vec2(250 * MPP, 330 * MPP); jDef.localAnchorB = new b2Vec2(0, 15 * MPP); _world.CreateJoint(jDef); // крепим 2-й кусок к первому jDef.bodyA = a; jDef.bodyB = b; jDef.localAnchorA = new b2Vec2(0, -15 * MPP); jDef.localAnchorB = new b2Vec2(0, 15 * MPP); _world.CreateJoint(jDef); // крепим 3-й кусок ко второму // локальные координаты крепления остаются те же, поэтому мы не указываем localAnchor-ы по новой jDef.bodyA = b; jDef.bodyB = c; _world.CreateJoint(jDef); // 4-й кусочек a = createPhysBox(_world, 300, 300, 5, 30); jDef.bodyA = c; jDef.bodyB = a; _world.CreateJoint(jDef); } /** * Создаёт физику круга * @param aWorld - мир бокса * @param aX - позиция объекта по X (пикс.) * @param aY - позиция объекта по Y (пикс.) * @param aRadius - радиус круга (пикс.) * @param aDensity - плотность * @param aFriction - трение * @param aRestitution - упругость * @return */ private function createPhysCircle(aWorld: b2World, aX: Number, aY: Number, aRadius: Number, aDensity: Number = 1.0, aFriction: Number = 1.0, aRestitution: Number = 0.5): b2Body { var bDef: b2BodyDef = new b2BodyDef(); var fixDef: b2FixtureDef = new b2FixtureDef(); fixDef.density = aDensity; fixDef.friction = aFriction; fixDef.restitution = aRestitution; fixDef.shape = new b2CircleShape(aRadius * MPP); bDef.position.Set(aX * MPP, aY * MPP); bDef.linearDamping = 0.0; bDef.angularDamping = 0.0; bDef.type = b2Body.b2_dynamicBody; var b: b2Body = aWorld.CreateBody(bDef); b.CreateFixture(fixDef); return b; } //Create standard boxes of given height , width at x,y /** * Создаёт физику прямоугольника * @param aWorld - мир бокса * @param aX - позиция по x (пикс.) * @param aY - позиция по y (пикс.) * @param aW - длина прямоугольника (пикс.) * @param aH - высота прямоугольника (пикс.) * @param aDensity - плотность * @param aFriction - трение * @param aRestitution - упругость * @return */ private function createPhysBox(aWorld: b2World, aX: Number, aY: Number, aW: Number, aH: Number, aDensity: Number = 1.0, aFriction: Number = 1.0, aRestitution: Number = 0.5): b2Body { var bDef: b2BodyDef = new b2BodyDef(); var fixDef: b2FixtureDef = new b2FixtureDef(); fixDef.density = aDensity; fixDef.friction = aFriction; fixDef.restitution = aRestitution; var shape: b2PolygonShape = new b2PolygonShape(); shape.SetAsBox(aW / 2 * MPP, aH / 2 * MPP); fixDef.shape = shape; bDef.position.Set(aX * MPP, aY * MPP); bDef.type = b2Body.b2_dynamicBody; var b: b2Body = aWorld.CreateBody(bDef); b.CreateFixture(fixDef); return b; } /** * Возвращает физ. объект под курсором мыши, если таковой имеется * @param includeStatic - учитывать статический тела? * @return объект или null */ private function GetBodyAtMouse(includeStatic:Boolean = false): b2Body { var mXWorldPhys: Number = mouseX * MPP; var mYWorldPhys: Number = mouseY * MPP; var mousePVec: b2Vec2 = new b2Vec2(mXWorldPhys, mYWorldPhys);//записываем текущие координаты курсора var aabb: b2AABB = new b2AABB(); // создаем прямоугольную область aabb.lowerBound.Set(mXWorldPhys - 0.001, mYWorldPhys - 0.001); //вокруг курсора мыши aabb.upperBound.Set(mXWorldPhys + 0.001, mYWorldPhys + 0.001); var body: b2Body = null; var fixture: b2Fixture; function GetBodyCallback(fixture:b2Fixture): Boolean { var shape:b2Shape = fixture.GetShape(); //получаем шейп который находится под курсором // если тело не статическое if (fixture.GetBody().GetType() != b2Body.b2_staticBody || includeStatic) { // проверяем находится ли точка-позиция курсора в рамках тела var inside: Boolean = shape.TestPoint(fixture.GetBody().GetTransform(), mousePVec); // если да if (inside) { body = fixture.GetBody(); //получаем ссылку на тело return false; } } return true; } _world.QueryAABB(GetBodyCallback, aabb); //проверяем на попадание любых тел в область aabb return body; //возвращаем тело } /** * Уничтожить текущее Mouse соединение */ private function destroyMouseJoint():void { if (_mJoint) _world.DestroyJoint(_mJoint); // удаляем соединение _mJoint = null; } // обновление физ. мира private function updateBox2D(aDTime: Number):void { if (_world) { _world.Step(aDTime, VEL_ITERS, POS_ITERS); _world.ClearForces(); // рисуем физику с помощью дебаг-рендера _world.DrawDebugData(); } } //} private ////////////////////////////////////////////////////////////////////////////////////////// //{ EVENTS private function onKeyDownHandler(e:KeyboardEvent):void { switch (e.keyCode) { case Keyboard.P: _statsParent.visible = !_statsParent.visible; break; case Keyboard.R: // пересоздаём сцену destroyScene(); createScene(); break; } } private function onMouseDown(e:MouseEvent):void { // если соединение уже есть, то уничтожаем его if (_mJoint) destroyMouseJoint(); // получаем тело под курсором var body:b2Body = GetBodyAtMouse(); if (body) { var md:b2MouseJointDef = new b2MouseJointDef(); // создаем настройки соединения md.bodyA = _world.GetGroundBody(); // один конец крепим к миру md.bodyB = body; // другой к телу md.target.Set(mouseX * MPP, mouseY * MPP); // соединение создается от курсора мыши md.collideConnected = true; md.maxForce = 3000; // макс. сила которая может быть приложена к телу _mJoint = _world.CreateJoint(md) as b2MouseJoint; // создаем соединение body.SetAwake(true); // будим тело } } private function onMouseUp(e:MouseEvent):void { destroyMouseJoint(); } private function onEnterFrameHandler(e:Event):void { update(1 / stage.frameRate); } //} events ////////////////////////////////////////////////////////////////////////////////////////// //{ PRIVATE public function update(aDTime: Number):void { updateBox2D(aDTime); // обновляем координаты соединения с мышью, если таковое имеется if (_mJoint) { var p2:b2Vec2 = new b2Vec2(mouseX * MPP, mouseY * MPP); _mJoint.SetTarget(p2); //перемещаем соединение за курсором мыши } } //} public } }
Всё самое важное и новое для нас находится в функции createObjs(), в ней создаются все объекты и соединения.
На данном этапе нужно хорошо понять 2 вещи: относительность точки соединения и угла соединения.
На Рисунке 1 представлена наглядность для понимания координат точки соединения localAnchor.
Теперь разберёмся с углом соединения referenceAngle. Если мы укажем угол соединения следующим образом:
jDef.referenceAngle = Math.PI / 6; // 30 градусов
то угол между телами станет = 30 градусов. Т.е. если первое тело будет статично (как в случае соединения мира и тела «a» в примере), то второе тело будет повернуто по часовой стрелке на 30 градусов.
Обратите внимание, что ось Y в системе координат box2d направлена вниз, поэтому поворот на положительный угол будет совершаться по часовой стрелке, а отрицательный угол — против часовой стрелки.
На Рисунке 2 схематично отображено соединение с углами 45, -135, 270 градусов.
Скачать исходники примера