среда, 7 июля 2010 г.

Интегрирование Верле (Rag doll physics)

Представляю вашему вниманию очередной вольный перевод главы из книги AdvancED ActionScript 3.0 Animation, автор Keith Peters.

Сегодня речь пойдет об интегрировании Верле, которое используется для программирования rag doll систем во многих физических движках.

С его помощью можно делать такие безделушки:

[SWF]http://coolisee.com/wordpress/wp-content/uploads/2010/07/07/Hinge.swf, 400, 400[/SWF]



[SWF]http://coolisee.com/wordpress/wp-content/uploads/2010/07/07/TestVerle.swf, 400, 400[/SWF]





Интегрирование Верле



Изначально интегрирование Верле было разработано для симуляции взаимодействий частиц. Если есть множество частиц, которые взаимодействуют друг с другом по определенным законам, на которые действует множество сил, то традиционные методы исчисления становятся слишком громоздкими для программирования систем частиц. Метод Верле был разработан как более эффективное и устойчивое решение.

Сегодня интегрирование Верле часто применяется для создания систем «физики тряпичной куклы» (rag doll physics) или программировании анимации персонажа. Технология стала популярной после публикации в 2003 году статьи «Продвинутая физика персонажей» (Advanced Character Physics (http://www.gamasutra.com/resource_guide/20030121/jacobson_01.shtml)) Томаса Якобсена. Код этого раздела основан на системе, описанной в этой статье.

Т.к. сильная сторона метода Верле вовсе не суперточность, как у метода Рунге-Кутта, я буду использовать анимацию основанную на кадрах, для того чтобы облегчить понимание работы этого метода (но нет никаких препятствий для вас, чтобы реализовать это с помощью анимации основанной на времени).

Одна из ключевых особенностей интегрирования Верле – то, что вы явно не используете скорость объекта для определения его положения. Вместо этого вы используете его последнюю позицию. Когда вам потребуется знание скорости объекта, вы вычитаете значение последней позиции из текущей позиции. Таким образом, каждый раз, когда вы перемещаете объект, вы изменяете его скорость. Это упрощает многие вещи. Скажем, есть объект с координатой х равной 100, и я перемещаю его в х равный 110. Когда происходит следующее обновление экрана, мы видим, как объект перемещается на 10 пикселей вправо от последней позиции объекта и берем значение 10 в качестве скорости по оси х. После каждого последующего обновления, старое значение х будет на 10 пикселей левее, нового, таким образом, объект будет двигаться по экрану вправо. Вы задаете скорость объекта, изменяя его позицию.

Другая общая черта интегрирования Верле – понятие связи между объектами. Два объекта могут быть соединены друг с другом и поддерживать определенную дистанцию между собой. Если они станут отдаляться или сближаться, то метод откорректирует их позиции, при этом, конечно, изменяя их скорость. Объекты могут иметь множественные связи, когда один объект связан с многими. Метод Верле очень эффективен при обработке всех этих отношений и поддержания расстояния между взаимодействующими объектами. Это отличный инструмент для того чтобы создавать сложные структуры, такие как «rag doll».

Хотя я продолжаю использовать слово «объекты», у перемещающихся объектов нет никакой определенной формы и они могут быть представлены совокупностью точек (point) (см рис 1). Линию соединяющую две точки назовем отрезком (stick). Два или более отрезка образуют структуру (structure). Две структуры или отрезка могут иметь связь (hinge).

Точка (point), отрезок (stick), структура (structure) и связь (hinge)
Рисунок 1. – Точка (point), отрезок (stick), структура (structure) и связь (hinge)



Начнем с точки.

Точки Верле



Мы создадим класс VerletPoint для того, чтобы формировать поведение точки, в соответствии с интегрированием Верле. Конечно, для точки необходимы свойства, в которых будут храниться текущие координаты объекта и свойства для хранения старых координат. Метод update определяет новые координаты точки, основываясь на ее старой позиции, используя разницу между новой и старой позицией в качестве скорости. Но перед этим мы должны сохранить текущую позицию объекта как «старую», для того, чтобы ее можно было использовать при следующем обновлении. Вот основная логика:

temp = currentPosition;
velocity = currentPosition – oldPosition;
currentPosition += velocity;
oldPosition = temp;


Тут все просто, поэтому перейдем к классу VerletPoint:

package
{
import flash.display.Graphics;
import flash.geom.Rectangle;

public class VerletPoint
{
public var x:Number;
public var y:Number;

private var _oldX:Number;
private var _oldY:Number;


public function VerletPoint(x:Number, y:Number)
{
setPosition(x, y);
}

public function update():void
{
var tempX:Number = x;
var tempY:Number = y;
x += vx;
y += vy;
_oldX = tempX;
_oldY = tempY;
}

public function setPosition(x:Number, y:Number):void
{
this.x = _oldX = x;
this.y = _oldY = y;
}

public function constrain(rect:Rectangle):void
{
x = Math.max(rect.left, Math.min(rect.right, x));
y = Math.max(rect.top, Math.min(rect.bottom, y));
}

public function set vx(value:Number):void
{
_oldX = x - value;
}
public function get vx():Number
{
return x - _oldX;
}

public function set vy(value:Number):void
{
_oldY = y - value;
}
public function get vy():Number
{
return y - _oldY;
}

public function render(g:Graphics):void
{
g.beginFill(0);
g.drawCircle(x, y, 4);
g.endFill();
}

}
}


Все предельно просто. Возможно вы зададитесь вопросом, почему есть геттеры и сеттеры для значений скорости по осям х и у, хотя раньше мы говорили, что интегрирование Верле не использует скорость явно. Эти геттеры и сеттеры не задают значения скорости напрямую. Например, когда вы используете set vx, происходит вычитание этого значения из текущего значения х и присваивания результата _oldX. Это гарантирует, что когда происходит обновление и вычитание _oldX из текущего, то мы получим в качестве скорости это самое значение, которое задается в set vx. Что касается геттера, то он просто вычитает старое значение из текущего.
Я также добавил метод setPosition, который присваивает определенное значение одновременно текущей и старой позиции. Это полезно, если вы хотите переместить точку в определенное местоположение, но не изменяя при этом скорость этой точки. Т.к. старая и новая позиция равны между собой, скорость будет равна нулю.
Метод constrain рассмотрим чуть позже, а пока разберем метод render. Т.к. VerletPoint это не отображаемый объект, мы не можем его видеть непосредственно на сцене. Метод render создает экземпляр класса Graphics и отрисовывает в нем небольшую точку, используя позицию VerletPoint.
Посмотрим на это в действии:

package {
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.geom.Rectangle;

public class VerletPointTest extends Sprite
{
private var _point:VerletPoint;
private var _stageRect:Rectangle;

public function VerletPointTest()
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;

_point = new VerletPoint(100, 100);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}

private function onEnterFrame(event:Event):void
{
_point.update();
graphics.clear();
_point.render(graphics);
}
}
}


Здесь в конструкторе мы создаем экземпляр класса VerletPoint и добавляем слушателя событию Event.ENTER_FRAME. В этом слушателе мы вызываем метод update() этой точки и затем очищаем объект graphics и вызываем метод render. Теперь, если вы протестируете этот класс, вы увидите точку, но она будет неподвижна. Добавим немного скорости. Вы можете сделать это пользуясь сеттерами для vx и vy. Например где-нибудь в конструкторе сделаем так:

_point.vx =5;


Это изменило бы старую позицию по оси х на 5 пикселей и скорость точки, таким образом, стала бы равна 5. Но мы можем с тем же успехом просто переместить нашу точку:

_point.x += 5;


В большинстве систем этот процесс только изменил бы позицию точки и не затронул ее скорость. Но в интегрировании Верле это также устанавливает значение скорость и ее направление. Для облегчения понимания это изменение можно рассматривать как толчок, который приводит в движение точку.
Когда вы применяете силу тяжести, у вас есть те же самые варианты. Или изменить значение vy или значение y. Добавление любой из этих строчек добавит силу тяжести к нашей точке:

 _point.vy =5;


или

_point.y +=5;



Сдерживание точек



Возможно, вы подумали, что неплохо бы оставить эти точки на сцене. Сдерживание точек в области это другая неотъемлемая и весьма простая часть интегрирования Верле. Все, что мы должны сделать, обеспечить нахождение этой точки в прямоугольнике, ограничивающем нашу сцену (или в любом другом прямоугольнике). Чтобы сделать это, мы удостоверяемся, что координата х точки не выходит за левый и правый край прямоугольника, а координата у не выходит за верхний и нижний край прямоугольника. Эта проверка происходит в методе constrain. В нашем основном классе мы создаем прямоугольник, описывающий область сцены (или любую другую область). Эту проверку мы производим в каждом кадре, перед вызовом метода update(). Код метода constrain класса VerletPoint:

		public function constrain(rect:Rectangle):void
{
x = Math.max(rect.left, Math.min(rect.right, x));
y = Math.max(rect.top, Math.min(rect.bottom, y));
}


А вот класс, которым это можно потестировать:

package {
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.geom.Rectangle;

public class VerletPointTest extends Sprite
{
private var _point:VerletPoint;
private var _stageRect:Rectangle;

public function VerletPointTest()
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
_stageRect = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);

_point = new VerletPoint(100, 100);
_point.x += 5;
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}

private function onEnterFrame(event:Event):void
{
_point.y += .5;
_point.constrain(_stageRect);
_point.update();

graphics.clear();
_point.render(graphics);
}
}
}



Отрезки Верле



Отрезок соединяет две точки. Он имеет свойство length, которое хранит расстояние между этими двумя точками. Если точки не выдерживают это расстояние, то оно автоматически восстанавливается изменением координат этих точек.

Класс VerletStick:

package
{
import flash.display.Graphics;

public class VerletStick
{
private var _pointA:VerletPoint;
private var _pointB:VerletPoint;
private var _length:Number;

public function VerletStick(pointA:VerletPoint, pointB:VerletPoint, length:Number = -1)
{
_pointA = pointA;
_pointB = pointB;
if(length == -1)
{
var dx:Number = _pointA.x - _pointB.x;
var dy:Number = _pointA.y - _pointB.y;
_length = Math.sqrt(dx * dx + dy * dy);
}
else
{
_length = length;
}
}

public function update():void
{
var dx:Number = _pointB.x - _pointA.x;
var dy:Number = _pointB.y - _pointA.y;
var dist:Number = Math.sqrt(dx * dx + dy * dy);
var diff:Number = _length - dist;
var offsetX:Number = (diff * dx / dist) / 2;
var offsetY:Number = (diff * dy / dist) / 2;
_pointA.x -= offsetX;
_pointA.y -= offsetY;
_pointB.x += offsetX;
_pointB.y += offsetY;
}

public function render(g:Graphics):void
{
g.lineStyle(0);
g.moveTo(_pointA.x, _pointA.y);
g.lineTo(_pointB.x, _pointB.y);
}
}
}


В конструкторе создаем два экземпляра класса VerletPoint и определяем свойство length. Если свойство length не задано явно, то вычисляем его значение, используя координаты двух точек. Метод render() рисует линию между двумя точками в экземпляре класса Graphics, который может использоваться при отладке. Также есть метод update(), который требует некоторого пояснения.

Сначала мы получаем расстояние между двумя точками и вычитаем его из свойства length отрезка. Это говорит нам насколько длиннее или короче отрезок, чем текущее свойство length. Эту разницу сохраняем в переменной diff (см рисунок 2).

Вычисление расстояния между двумя точками и разницы между текущей длиной и заданной
Рисунок 2. – Вычисление расстояния между двумя точками и разницы между текущей длиной и заданной



Используя простую тригонометрию получим значения для отклонений длины по осям х и у (см рисунок 3):

diff * dx / dist;
diff * dy / dist;


Вычисление составляющих разницы между текущей и требуемой длиной
Рисунок 3. – Вычисление составляющих разницы между текущей и требуемой длиной



Заметим, что мы делим полученные значения на 2, т.к. будем перемещать обе точки на половину значения полученной разницы. Наконец мы берем эти переменные offsetX и offset и вычитаем их из позиции первой точки и прибавляем к позиции второй. Это помещает их на большее расстояние друг от друга и дает требуемое значение свойству length (см рисунок 4).

Перемещаем каждую точку на половину разницы между текущим расстоянием и требуемым
Рисунок 4. – Перемещаем каждую точку на половину разницы между текущим расстоянием и требуемым



Давайте протестируем этот отрезок:

package {
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.geom.Rectangle;

public class VerletStickTest extends Sprite
{
private var _pointA:VerletPoint;
private var _pointB:VerletPoint;
private var _stick:VerletStick;
private var _stageRect:Rectangle;

public function VerletStickTest()
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
_stageRect = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);

_pointA = new VerletPoint(100, 100);
_pointB = new VerletPoint(105, 200);

_stick = new VerletStick(_pointA, _pointB);

addEventListener(Event.ENTER_FRAME, onEnterFrame);
}

private function onEnterFrame(event:Event):void
{
_pointA.y += .5;
_pointA.update();
_pointA.constrain(_stageRect);

_pointB.y += .5;
_pointB.update();
_pointB.constrain(_stageRect);

_stick.update();

graphics.clear();
_pointA.render(graphics);
_pointB.render(graphics);
_stick.render(graphics);
}
}
}


Как видите, сначала мы создаем точки, затем отрезок, передавая для этого в конструктор две точки. Затем в слушателе onEnterFrame() обновляем отрезок после обновления точек и все рендерим.

Когда вы протестируете этот класс, вы увидите, что создается отрезок и падает вниз сцены. Можно заметить, что есть небольшая реакция на столкновение с нижним краем сцены. Но почему это случается, если мы это не планировали и не кодировали? Это происходит, потому, что код обновления отрезка проверяет расстояние между точками и увеличивает координату у нижней точки, а код удержания отрезка на сцене наоборот уменьшает. Эти два действия кратковременно борются друг с другом, что и создает симуляцию реакции на столкновение с нижним краем сцены.

Фактически, можно сказать, что это баг. Я так говорю, потому что мы явно не кодировали это поведение, и кто-то может отметить его как нежелательное. Чтобы избавиться от реакции мы должны несколько раз вызвать методы update и constrain для отрезка и точки соответственно, чтобы уменьшить амплитуду перемещения отрезка от действия этих двух методов. Изменим метод onEnterFrame() следующим образом:

private function onEnterFrame(event:Event):void
{
_pointA.y += .5;
_pointA.update();

_pointB.y += .5;
_pointB.update();

for(var i:int = 0; i < 5; i++)
{
_pointA.constrain(_stageRect);
_pointB.constrain(_stageRect);
_stick.update();
}

graphics.clear();
_pointA.render(graphics);
_pointB.render(graphics);
_stick.render(graphics);
}


Посмотрим, как это работает. Многократный вызов методов обновления и сдерживания для отрезка и точек позволяют им достигнуть некоторого консенсуса, относительно того, где эти точки должны располагаться. Т.к. в этом примере нет действия силы тяжести и принудительного обновления позиции в каждом кадре, а есть только «неявная» скорость, полученная из разницы позиций в начале и конце итерации, то никакой реакции тут быть не должно. Этот отрезок должен больше походить своим поведением на стальной прут, а не на зеленую ветку. Вы можете поменять количество итераций, чтобы изменить поведение этого отрезка – много итераций – малая реакция, одна итерация – большая реакция.

Однако следует понимать, что эти итерации могут повлечь снижение производительности вашего приложения, если отрезков и точек много. Поэтому применять их стоит только там, где это действительно необходимо.


Структуры Верле



Структуры Верле состоят из нескольких отрезков или точек, простейшая структура – треугольник. Мы создаем три точки и соединяем их тремя отрезками:

package {
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.geom.Rectangle;

public class Triangle extends Sprite
{
private var _pointA:VerletPoint;
private var _pointB:VerletPoint;
private var _pointC:VerletPoint;
private var _stickA:VerletStick;
private var _stickB:VerletStick;
private var _stickC:VerletStick;
private var _stageRect:Rectangle;

public function Triangle()
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
_stageRect = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);

_pointA = new VerletPoint(100, 100);
_pointB = new VerletPoint(200, 100);
_pointC = new VerletPoint(150, 200);

_stickA = new VerletStick(_pointA, _pointB);
_stickB = new VerletStick(_pointB, _pointC);
_stickC = new VerletStick(_pointC, _pointA);

addEventListener(Event.ENTER_FRAME, onEnterFrame);
}

private function onEnterFrame(event:Event):void
{
_pointA.y += .5;
_pointA.update();

_pointB.y += .5;
_pointB.update();

_pointC.y += .5;
_pointC.update();

for(var i:int = 0; i < 1; i++)
{
_pointA.constrain(_stageRect);
_pointB.constrain(_stageRect);
_pointC.constrain(_stageRect);
_stickA.update();
_stickB.update();
_stickC.update();
}

graphics.clear();
_pointA.render(graphics);
_pointB.render(graphics);
_pointC.render(graphics);
_stickA.render(graphics);
_stickB.render(graphics);
_stickC.render(graphics);
}
}
}


Здесь у нас есть три точки: А, В и С. Каждый отрезок соединяет две точки и тем самым образует треугольник. Тестируя этот класс, мы увидим треугольник, падающий вниз сцены. Треугольник обладает небольшой реакцией на столкновение, изменяя итератор i эту реакцию можно значительно уменьшить.

Теперь посмотрим на квадрат, это наша первая попытка:

package {

import adobe.utils.CustomActions;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.geom.Rectangle;

public class Square1 extends Sprite {

private var _pointA:VerletPoint;
private var _pointB:VerletPoint;
private var _pointC:VerletPoint;
private var _pointD:VerletPoint;
private var _stickA:VerletStick;
private var _stickB:VerletStick;
private var _stickC:VerletStick;
private var _stickD:VerletStick;
private var _stageRect:Rectangle;

public function Square1 () {
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
_stageRect = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);
_pointA = new VerletPoint(100, 100);
_pointB = new VerletPoint(200, 100);
_pointC = new VerletPoint(200, 200);
_pointD = new VerletPoint(100, 200);

_stickA = new VerletStick(_pointA, _pointB);
_stickB = new VerletStick(_pointB, _pointC);
_stickC = new VerletStick(_pointC, _pointD);
_stickD = new VerletStick(_pointD, _pointA);

addEventListener(Event.ENTER_FRAME, onEnterFrame);
}

private function onEnterFrame(e:Event):void {
_pointA.y += 0.5;
_pointA.update();

_pointB.y += 0.5;
_pointB.update();

_pointC.y += 0.5;
_pointC.update();

_pointD.y += 0.5;
_pointD.update();

for (var i:int = 0; i < 1; i++) {
_pointA.constrain(_stageRect);
_pointB.constrain(_stageRect);
_pointC.constrain(_stageRect);
_pointD.constrain(_stageRect);
_stickA.update();
_stickB.update();
_stickC.update();
_stickD.update();
}

graphics.clear();
_pointA.render(graphics);
_pointB.render(graphics);
_pointC.render(graphics);
_pointD.render(graphics);
_stickA.render(graphics);
_stickB.render(graphics);
_stickC.render(graphics);
_stickD.render(graphics);
}
}
}


Создаем четыре точки и соединяем их линиями, правильно? Все выглядит правильно, до тех пор, пока квадрат не приземлится на нижний край сцены, наш квадрат ломается как карточный домик. Давайте укрепим наш квадрат, чтобы он не разваливался. Мы можем сделать это с помощью еще одного отрезка. Объявите еще одну переменную _stickE и соедините точки A и C.

_stickE = new VerletStick(_pointA, _pointC);


Убедитесь что обновления и рендеринг происходят в методе onEnterFrame(). Для того, чтобы сделать наш квадрат еще более жестким, соедините точки B и D.
Если падение без вращения кажется вам скучным, можно изменить координаты какой-либо точки сразу после ее создания:

_pointA = new VerletPoint(100, 100);
_pointA.vx = 10;


Прежде, чем двигаться дальше подумаем об очистке этого кода. Даже четыре точки и пять отрезков создают неопрятный код. Будем хранить все наши отрезки и точки в разных массивах, а вызывать методы обновления и сдерживания с помощью циклов, в которых будут обходиться все элементы этих массивов:

package {
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.geom.Rectangle;

public class Square extends Sprite
{
private var _points:Array;
private var _sticks:Array;
private var _stageRect:Rectangle;

public function Square()
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
_stageRect = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);

_points = new Array();
_sticks = new Array();

var pointA:VerletPoint = makePoint(100, 100);
pointA.vx = 10;
var pointB:VerletPoint = makePoint(200, 100);
var pointC:VerletPoint = makePoint(200, 200);
var pointD:VerletPoint = makePoint(100, 200);

makeStick(pointA, pointB);
makeStick(pointB, pointC);
makeStick(pointC, pointD);
makeStick(pointD, pointA);
makeStick(pointA, pointC);

addEventListener(Event.ENTER_FRAME, onEnterFrame);
}

private function onEnterFrame(event:Event):void
{
updatePoints();

for(var i:int = 0; i < 1; i++)
{
constrainPoints();
updateSticks();
}

graphics.clear();
renderPoints();
renderSticks();
}

private function makePoint(xpos:Number, ypos:Number):VerletPoint
{
var point:VerletPoint = new VerletPoint(xpos, ypos);
_points.push(point);
return point;
}

private function makeStick(pointA:VerletPoint, pointB:VerletPoint, length:Number = -1):VerletStick
{
var stick:VerletStick = new VerletStick(pointA, pointB, length);
_sticks.push(stick);
return stick;
}

private function updatePoints():void
{
for(var i:int = 0; i < _points.length; i++)
{
var point:VerletPoint = _points[i] as VerletPoint;
point.y += .5;
point.update();
}
}

private function constrainPoints():void
{
for(var i:int = 0; i < _points.length; i++)
{
var point:VerletPoint = _points[i] as VerletPoint;
point.constrain(_stageRect);
}
}

private function updateSticks():void
{
for(var i:int = 0; i < _sticks.length; i++)
{
var stick:VerletStick = _sticks[i] as VerletStick;
stick.update();
}
}

private function renderPoints():void
{
for(var i:int = 0; i < _points.length; i++)
{
var point:VerletPoint = _points[i] as VerletPoint;
point.render(graphics);
}
}

private function renderSticks():void
{
for(var i:int = 0; i < _sticks.length; i++)
{
var stick:VerletStick = _sticks[i] as VerletStick;
stick.render(graphics);
}
}
}
}



Связи



Теперь, когда у нас есть код структур, мы можем повеселиться, создавая различные формы. Это хороший повод для того, чтобы поговорить о связях, которые соединяют две структуры одной точкой. Они могут иметь полную свободу движения, вращаясь вокруг этой точки.

Здесь мы сделаем простой маятник.

package {
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.geom.Rectangle;

public class Hinge extends Sprite
{
private var _points:Array;
private var _sticks:Array;
private var _stageRect:Rectangle;

public function Hinge()
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
_stageRect = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);

_points = new Array();
_sticks = new Array();

// base
var pointA:VerletPoint = makePoint(stage.stageWidth / 2, stage.stageHeight - 500);
var pointB:VerletPoint = makePoint(0, stage.stageHeight);
var pointC:VerletPoint = makePoint(stage.stageWidth, stage.stageHeight);

// arm
var pointD:VerletPoint = makePoint(stage.stageWidth / 2 + 350, stage.stageHeight - 500);

// weight
var pointE:VerletPoint = makePoint(stage.stageWidth / 2 + 360, stage.stageHeight - 510);
var pointF:VerletPoint = makePoint(stage.stageWidth / 2 + 360, stage.stageHeight - 490);
var pointG:VerletPoint = makePoint(stage.stageWidth / 2 + 370, stage.stageHeight - 500);

// base
makeStick(pointA, pointB);
makeStick(pointB, pointC);
makeStick(pointC, pointA);

// arm
makeStick(pointA, pointD);

// weight
makeStick(pointD, pointE);
makeStick(pointD, pointF);
makeStick(pointE, pointF);
makeStick(pointE, pointG);
makeStick(pointF, pointG);

addEventListener(Event.ENTER_FRAME, onEnterFrame);
}

private function onEnterFrame(event:Event):void
{
updatePoints();

for(var i:int = 0; i < 1; i++)
{
constrainPoints();
updateSticks();
}

graphics.clear();
renderPoints();
renderSticks();
}

private function makePoint(xpos:Number, ypos:Number):VerletPoint
{
var point:VerletPoint = new VerletPoint(xpos, ypos);
_points.push(point);
return point;
}

private function makeStick(pointA:VerletPoint, pointB:VerletPoint, length:Number = -1):VerletStick
{
var stick:VerletStick = new VerletStick(pointA, pointB, length);
_sticks.push(stick);
return stick;
}

private function updatePoints():void
{
for(var i:int = 0; i < _points.length; i++)
{
var point:VerletPoint = _points[i] as VerletPoint;
point.y += .5;
point.update();
}
}

private function constrainPoints():void
{
for(var i:int = 0; i < _points.length; i++)
{
var point:VerletPoint = _points[i] as VerletPoint;
point.constrain(_stageRect);
}
}

private function updateSticks():void
{
for(var i:int = 0; i < _sticks.length; i++)
{
var stick:VerletStick = _sticks[i] as VerletStick;
stick.update();
}
}

private function renderPoints():void
{
for(var i:int = 0; i < _points.length; i++)
{
var point:VerletPoint = _points[i] as VerletPoint;
point.render(graphics);
}
}

private function renderSticks():void
{
for(var i:int = 0; i < _sticks.length; i++)
{
var stick:VerletStick = _sticks[i] as VerletStick;
stick.render(graphics);
}
}
}
}


Этот код создает треугольное основание с маятником, присоединенным к верхней точке основания. К маятнику присоединен груз и он раскачивается. Грузик может быть присоединен к маятнику и своей вершиной, но это не столь важно из-за малых размеров груза.

Комментариев нет:

Отправить комментарий