« FOAM: физика и actionscript 3. Часть 2 | Velocity Фильтр » |
Phys2D: физика и java
Эта статья является логическим продолжением ранее опубликованной серии посвященной 2d-физике для flash-разработчиков. Сегодня я продолжу рассказ о методах “плоской” физической симуляции, вот только язык программирования будет java. Сравнивать эти две платформы (java и flash) практически не возможно: отличаются и технические средства языка и цели, которые ставят перед собой идеологи java и flash. Основная сфера применения java – это “большие” корпоративные приложения, базы данных, сервера приложений, работающие с множеством клиентов. Flash первоначально позиционировался на рынке как средство для создания “блестящих” баннеров. Как только в массовом сознании сложилась мысль, что в internet можно размещать не только html-страницы, но интерактивные приложения, то adobe решила перепозиционировать flash, “обозвала” его платформой для разработки RIA-приложений, выпустила flex, air. Очевидно, что и flash и java имеют достаточное количество общих сфер применения, например, игры. Конечно, на рынке гораздо большее количество игр (я говорю про небольшие игрушки) написанных на flash, чем на java (с большими играми ситуация в точности противоположная). В любом случае, для того чтобы некоторая технология завоевала внимание разработчиков необходимо достаточное количество прикладных библиотек и учебных пособий. По мере своих скромных сил я исправляю эту ситуацию и рассказываю об интересных, но не “раскрученных” библиотеках и подходах в программировании. Сегодня время помочь всем, кто хочет сделать на java небольшую игру (или серьезное приложение) активно использующую “настоящую” физику.Библиотека phys2d (домашний сайт www.cokeandcode.com) идеально подходит для желающих создать java-приложение выполняющее симуляции “плоских” объектов. Код библиотеки ориентирован на java 1.4 (выпущена года эдак 4-е назад), а значит, что для его работы клиентам, наверняка, не нужно будет обновлять версию среды исполнения java. Библиотека регулярно обновляется, и последняя версия датирована концом 2007 г (в svn репозитории на google code есть и более свежая версия). После того как вы загрузили библиотеку и подключили ее к вашей IDE, самое время обратить внимание на идущие в комплекте примеры. Два с лишним десятка примеров покажут вам как работать со всеми возможностями phys2d. Каждый класс примера является наследником от класса AbstractDemo, внутри которого создается каркас приложения и содержится код отрисовки моделируемого мирка. Что бы не занимать место и не копировать чужой код, я буду все свои примеры создавать как расширения класса AbstractDemo (однако перед этим я советую просмотреть его исходники).
Итак, “сердце” симуляции с помощью phys2d это класс World. Когда мы его создаем, то нужно указать как параметры конструктора направление действующей силы гравитации (она задается с помощью вектора Vector2f), затем количество итераций выполняемых для расчета каждого шага симуляции (чем больше это число, тем точнее симуляция, но и выше затраты ресурсов), и последний параметр – стратегия первичного обнаружения столкновений. QuadSpaceStrategy предполагает, что пространство будет рекурсивно разбиваться на четыре подобласти до тех пор, пока в каждом из сегментов не останется не более 20 тел, или будет выполнено не более 5-и разбиений (см. параметры конструктора). Кроме QuadSpaceStrategy стратегии есть еще BruteCollisionStrategy, которая предполагает выполнение прямого перебора всех пар и проверку их на коллизию (но считайте, что про это “достижение” phys2d я ничего не говорил). Важно: отрисовка физической модели целиком на вашей совести: никаких средств для визуализации в состав phys2d не входит. С другой стороны в примерах показано как выполнить отрисовку объектов “мирка” в виде контуров (в классе AbstractDemo обратите внимание на метод draw). Давайте создадим “мир”:
protected World world = new World(new Vector2f(0.0f, 10.0f), 10, new QuadSpaceStrategy(20,5));
Метод | Описание |
addForce | Метод служит для добавления силы действующей на объект (суммируя новую и старую силу). |
setForce | Устанавливает значение силы примененной к объекту, замещая старую. |
getMass | Получает значение массы объекта и устанавливает его (вместо массы можно использовать специальное значение Body.INFINITE_MASS, которое означает, что тело обладает бесконечной массой). Здесь и далее: если есть метод getX, то должен быть и метод setX для установки значения параметра тела. |
getI | Получить величину инерции тела. |
getRotation | Получить значение величины вращения тела. |
getTorque | Получить значение вращательного момента. |
getFriction | Получить значение коэффициента трения (характеристика материала, из которого изготовлен объект). |
getConnected | Получить список объектов, с которым наш объект соединен. |
Очень приятно, что к каждому из “тел” можно присоединить пользовательский объект с помощью метода setUserData (например, к модели “машинка” мы можем присоединить объект, хранящий сведения о количестве оставшихся жизней или топлива). Также у объекта есть специальные флажки, управляющие тем, будет ли ему позволено перемещаться и вращаться под действием других тел (по умолчанию они установлены в true), но можно и запретить такое поведение с помощью методов (setRotatable и setMoveable). Также можно исключить данный объект из списка тех тел, на которые действует гравитация (мы задавали ее для всей сцены при создании объекта World). Для этого используйте метод setGravityEffected (также воздействию гравитации не подвержены те тела, масса которых бесконечна). Phys2d позволяет настроить списки объектов, которые не могут сталкиваться (они будут проходить сквозь друг друга как приведения). Для этого используйте два метода addExcludedBody и removeExcludedBody. Одним словом параметров очень много, но вот только я нигде не упомянул о форме этого тела. Body – это что? Body – это обертка для еще одного объекта: Shape (форма тела). Т.е. когда мы создаем Body, мы тут же должны создать еще один объект производный от класса AbstractShape (или поддерживающий интерфейс Shape), и эту ссылку на “фигуру” нужно передать в конструктор “тела”, например, так:
Body body2 = new Body("Body2", ФИГУРА, 100.0f);
StaticBody wall_1 = new StaticBody("Box1", new Box(100,100));
Box – обычный прямоугольник, при его создании необходимо конструктору передать ширину и высоту прямоугольника.
Circle – Для создания круга нужно передать как единственный параметр конструктору значение радиуса.
Polygon – не совсем обычный полигон. Для его создания необходимо передать конструктору список вершин. Необычность полигона в том, что (в отличие от реализации полигона в FOAM) он может не быть выпуклым. Надо сказать, что есть и другая разновидность полигона требующая, чтобы он был выпуклым (ConvexPolygon).
Line – Для создания линии мы передаем конструктору координаты точки.
И тут самое время задуматься, а позвольте спросить, где указывается центр этих фигур? Или, например, Line, чтобы ее задать, нам нужны две точки, но ведь конструктор Line принимает только одну координату, где вторая? Центр координат фигур это не часть Shape, а часть Body. После создания Body мы выполним его перемещение в некоторую точку, например, так:
Body body2 = new Body("Body2", new Box(100.0f, 100.0f), 100.0f);
body2.setPosition(200.0f, 200.0f);
создаем первую стену
StaticBody wall_1 = new StaticBody(new Box(200,5));
позиционируем ее
wall_1.setPosition(100, 300);
вращаем фигуру
wall_1.setRotation((float)(Math.PI / 4));
устанавливаем силу трения
wall_1.setFriction (1f);
и добавляем ее в "мир"
world.add (wall_1);
аналогично и для второй стены
StaticBody wall_2 =new StaticBody(new Box(200,5));
wall_2.setPosition(300, 300);
wall_2.setRotation(-(float)(Math.PI / 4));
упавшие на эту стену кубики будут плавно “соскальзывать вниз”
wall_2.setFriction(.0f);
world.add (wall_2);
for (int i = 0; i < 5; i++){
создаем динамическое "тело" в виде падающего кубика
Body body = new Body(new Box (15, 15), 5);
body.setPosition((float)(100 + 300 * Math.random()),50);
world.add (body);
}
увеличим силу трения воздуха
world.setDamping(1f);
и изменим силу гравитации, действующую на все динамические объекты
world.setGravity(0,1);
Вы можете спросить, а где скрывается код запускающий симуляцию? Ведь когда я рассказывал про FOAM, то использовал методы simulate и stop для запуска и остановки симуляции? Не забывайте, что наш класс примера был наследован от AbstractDemo, внутри которого и реализован главный цикл приложения вызывающий метод step (сделать шаг симуляции). Также нам по наследству досталась обработка нескольких клавиш: “R” – выполняет сброс симуляции, а клавиша “C” добавляет к рисуемым контурам фигур еще и отображение точек контакта и нормалей к ним.
Помимо объектов есть еще и сочленения, соединяющие между собой фигуры. Все сочленения должны поддерживать интерфейс Joint. По сравнению с FOAM и его “куцыми” ограничениями (Spring и Bungee) Phys2d просто великолепен. Самым простым видом сочленения является “жесткое” (FixedJoint). При создании FixedJoint нужно просто указать два тела, которые мы хотим соединить. В примере ниже обратите внимание на то, что только один из кругов является статическим, второй – нет и должен падать под действием силы тяжести. Однако он останется неподвижным т.к. жестко “приколочен” к первому кругу.
создаем два круга
Body ball1 = new StaticBody(new Circle(20));
ball1.setPosition(350, 100);
world.add (ball1);
Body ball2 = new Body(new Circle(20), 1);
ball2.setPosition(450, 100);
world.add (ball2);
и создаем соединение между телами
Joint bj_1 = new FixedJoint(ball2, ball1);
world.add(bj_1);
создаем потолок
StaticBody ceil=new StaticBody(new Box (400, 5));
ceil.setPosition(250, 50);
создаем скамейку
Body bench =new Body(new Box (200, 5), 10);
bench.setPosition(250, 300);
добавляем эти два объекта на сцену
world.add (bench);
world.add (ceil);
и создаем соединение между телами
BasicJoint bj_1 = new BasicJoint(bench, ceil, new Vector2f(200,200));
world.add(bj_1);
Пригодится и такой вид соединения как DistanceJoint, он служит для задания жесткой шарнирной связи между двумя телами. Например, следующий код, создает два круга, один из которых является статическим. Второй же расположен на шарнире сбоку от первого и как только симуляция будет запущена, то второй круг начнет вращаться как на “качелях” вокруг первого круга, не удаляясь и не приближаясь (храня дистанцию между кругами равной 100).
создаем два круга
Body ball1 = new StaticBody(new Circle(20));
ball1.setPosition(350, 100);
world.add (ball1);
Body ball2 = new Body(new Circle(20), 1);
ball2.setPosition(450, 100);
world.add (ball2);
и создаем соединение между телами
DistanceJoint bj_1 = new DistanceJoint(ball1, ball2, new Vector2f(0,10), new Vector2f(0,10), 100);
world.add(bj_1);
создаем потолок
Body ceil=new StaticBody(new Box (400, 5));
ceil.setPosition(250, 50);
world.add (ceil);
Body [] ball = new Body[10];
создаем массив из десятки кружков расположенных в форме эллипса
for (int i= 0; i < ball.length; i++){
ball[i] = new Body(new Circle(10),1);
ball[i].setPosition(
(float)(200 + 50 * Math.cos(Math.PI * i/ 5)),
(float)(200 + 100 * Math.sin(Math.PI * i/ 5))
);
world.add(ball[i]);
}
теперь создаем попарные связи между кружками
for (int i= 0; i < ball.length - 1; i++){
world.add (new SpringJoint(ball[i], ball[1+i], ball[i].getPosition(), ball[1+i].getPosition()));
}
последняя связь между одним из кругов и “потолком”
world.add(new SpringJoint(ball[ball.length-1], ceil,
ball[ball.length-1].getPosition(), ceil.getPosition()));
Гораздо интереснее посмотреть, а какие средства для анализа происходящей на сцене ситуации есть в составе phys2d? Можно ли спросить, какие объекты столкнулись между собой, столкнулась ли, например, модель машинки со столбом или нет? Такие средства есть, хотя и не очень удобные. Возвратимся к рассмотрению устройства класса AbstractDemo: в главном цикле симуляции после расчете физики и отрисовки фигур вызывается метод update, внутри которого и следует выполнить обращение к “мирку” с вопросом: “дай-ка мне список всех контактов для вот этого тела”. В примере ниже я создал “пол” в виде статического прямоугольника и бросил на него ряд мячиков. Затем внутри метода update я получаю массив коллизий шариков с “полом” и вывожу их на экран.
protected void update() {
super.update();
CollisionEvent[] colls = world.getContacts(floor);
if (colls.length > 0){
System.out.println("was collision: ");
for (CollisionEvent col : colls) {
System.out.println("--"+ col.getBodyA() + " * "+ col.getBodyB());
}
}
}
Примеры роликов показывающих возможности phys2d
« Velocity Фильтр | Java аннотации. Пример 1 » |