Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Учебник по GLScene.doc
Скачиваний:
255
Добавлен:
16.12.2018
Размер:
7.18 Mб
Скачать

Глава 31. Физика ode.

Open Dynamics Engine – физический движок, который использовался в таких известных играх как BloodRayne2, Stalker, Racer и во многих других.

Есть два различных варианта работы с ODE. Здесь будут рассмотрены оба, но пока скажу о них вкратце. В GLScene имеются два встроеных компонента для работы с ODE: GLODEManager и GLODEJointList. Но можно их игнорировать и работать с чистым ODE.

Многие начинающие программисты выбирают встроенные компоненты. Но этот “лёгкий” путь оказывается вовсе не таким.

Во-первых, с помощью этих компонентов НЕЛЬЗЯ написать сложной физики, в лучшем случае выйдет простенькая аркадная гонка.

Во-вторых, любому программисту даже при использовании ODEManager’а все равно придется использовать немного (а может и много) чистого кода ODE.

Ну и, наконец, физика – второй по сложности расчетов элемент 3D игры после графики, а ODE на VCL работает почти в 1,2 раза медленнее, чем без него.

Сразу скажу одно – ODE – это не часть GLScene, она не была написана для GLScene, и ODEManager и GLODEJointList является лишь надстройкой над самой библиотекой. Следовательно, можно написать свои компоненты или просто модули, совершенно независимые от встроенных в GLScene.

I. Clear ODE.

Для начала для всех проектов с ODE понадобится модуль ODEImport. Главная цель ODE – рассчитать взаимодействие предметов в трехмерном мире. Базовое понятие ODE – Мир (World), в котором находятся все тела. Сначала необходимо создать новый Мир. Для этого необходима переменная типа pdxWorld. Собственно создается мир просто: var World: pdxWorld; World := dWorldCreate; Для мира можно настроить много параметров (притяжение и т.д.), которые можно найти в официальной документации. Одной программе вряд ли понадобиться более одного мира. Идем дальше. В любом мире есть Тела (Body). Тела не имеют формы и размера, но в них определяется много параметров, как масса, плотность и т.д. Тела не взаимодействуют друг с другом, на них лишь действуют силы. Создать новое тело также просто: var Body: pdxBody; Body := dBodyCreate (World); т.е. при создании указываем, к какому миру оно принадлежит. Теперь про взаимодействие тел. Для того, чтобы тела могли сталкиваться, к ним необходимо присоединить Геометрию (Geometry). Геометрия бывает перемещаемой (все, что может двигаться) и неперемещаемой (статической). Например, человек – перемещаемая, а дом - нет. К одному телу можно присоединить сколько угодно геометрии. Создание геометрии: var Geom: pdxGeom; Geom := dCreateXXX (const Space: pdxSpace; параметры_геометрии); где XXX – тип создаваемого объекта: Sphere (сфера), Box (параллелепипед), Plane (плоскость), Cylinder (цилиндр с верхушкой или капсула), Ray (луч) и TriMesh (трехмерная сетка); Space – пространство столкновений (смотри дальше); параметры геометрии – конкретные параметры для данной геометрии (радиус для сферы и т.д.). Далее необходимо присоединить тело к геометрии: dGeomSetBody (Geom: pdxGeom; Body: pdxBody); (здесь, думаю, все ясно).

Когда создана вся Геометрия, необходимо определить столкновение между ней на каждом шаге (например, в Cadencer’е):. function dCollide (o1, o2: pdxGeom; flags: integer; var Contacts: TdContactGeom; Skip: integer): integer) Здесь o1, o2 – Геометрия, между которой может быть столкновение, flags – определяет, как информация о контакте должна быть сгенерирована, если произошло касание (см. официальную документацию), Contacts - указывает на массив TdContactGeom структур: TdContactGeom = record pos: TdVector3; //Позиция точки столкновения normal: TdVector3; //Нормаль к точке столкновения depth: TdReal; //Глубина столкновения g1, g2: PdxGeom; //Столкнувшиеся тела end; skip - смещение одной dContactGeom от другой (обычно skip := SizeOf(TdContactGeom)). Если Геометрии пересекаются, эта функция возвращает количество точек контакта (и обновляет Сontacts массив), в противном случае возвращается ноль (и Сontact массив не затрагивается). Вот важная выписка из документации по ODE: «Учтите что геометрия - это не тоже самое, что тело, в том плане, что геометрия имеет геометрические свойства (размер, форму, позицию и ориентацию), но не физические свойства (такие как скорость или масса). Тело и Геометрия вместе представляют все свойства имитируемого объекта».

Теперь поговорим о Пространствах (Space). Пространство – это нечто вроде мира для тел, но только для геометрии. Тем не менее, геометрия может принадлежать какому-то пространству, а может и не принадлежать, она все равно может сталкиваться. Пространства бывают простыми и многомерными. Создание простого пространства:

var Space: pdxSpace;

Space := dSimpleSpaceCreate(ParentSpace);

Здесь ParentSpace – имя родительского пространства или nil, если таковое отсутствует.

Создание многомерного пространства:

var Space: pdxSpace;

space:= dHashSpaceCreate(ParentSpace);

Здесь ParentSpace – имя родительского пространства или nil, если таковое отсутствует.

Что бы лучше понять различия между простым и многомерным пространством, я приведу вырезку из документации к ODE: «Допустим, у нас есть две машины, перемещающиеся по поверхности земли. Каждая машина состоит из множества Геометрий. Если всю Геометрию машин разместить в одном пространстве, то время необходимое на определение столкновения между машинами будет пропорционально общему количеству Геометрии (или даже количеству геометрии в квадрате в зависимости от типа пространства). Увеличить скорость определения столкновений можно, представив каждую машину своим пространством. Геометрию машин разместить в пространстве-машин (car-spaces), а пространства машин разместить в пространстве высшего уровня. На каждом шаге времени dSpaceCollide будет вызываться для пространства высшего уровня. Здесь будет происходить проверка на пересечение пространств-машин между собой (а именно между ограничивающими параллелепипедами) и, если пересечение произошло, будет вызываться функция обратного вызова (nearCallback). Функция обратного вызова затем может проверить геометрию в пространствах-машин с помощью функции dSpaceCollide2. Если машины находятся не рядом, то функция обратного вызова не будет вызываться. Таким образом не будет впустую тратиться время на ненужный тест». Немного поясню. procedure dSpaceCollide (const Space: PdxSpace; data: pointer; callback: TdNearCallback); Определяет столкновения между геометрией в пространстве, и в случае столкновения пары геометрий, вызывает nearCallback. Эта процедура определяет, что делать со столкнувшейся геометрией. Указатель data может использоваться для передачи данных, установленных пользоывателем, а если таких данных нет, устанавливайте его в nil.

procedure dSpaceCollide2 (o1, o2: PdxGeom; data: pointer; callback: TdNearCallback); отличается от dSapceCollide только тем, что выясняет столкновение не только между двумя пространствами, но и между двумя геометриями из разных пространств или одной геометрией из одного пространства и всеми геометриями другого или двумя пространствами.

Вот типичный код процедуры nearCallback. procedure nearCallback (data: pointer; o1, o2: PdxGeom); cdecl;

var

i, n: integer;

b1, b2: PdxBody;

c: TdJointID;

begin

b1 := dGeomGetBody(o1); //b1 - первая проверяемая геометрия

b2 := dGeomGetBody(o2); //b2 - вторая проверяемая геометрия

if Assigned(b1) and Assigned(b2) then

if (dAreConnected(b1, b2) <> 0) then

Exit;

n := dCollide (o1, o2, ContactNum, contact[0].geom, sizeof(TdContact)); //получения точек контакта для каждой пары геометрии

for i := 0 to n-1 do

begin

with contact[i] do

begin

surface.mode := ord(dContactBounce) or ord(dContactSoftcfm) or ord(dContactSoftERP);

surface.mu := 0.6; // трение

surface.soft_erp := 0.5; //праметр кменьшения ошибки в сочлинении

surface.soft_cfm := 0.001; //смешивает результирующую силу соединения в том соединении в котором она возникла.

surface.bounce := 0.6; //пружинистость

c := dJointCreateContact (world, contactgroup, @contact[i]); // Контактное сочленение предотвращает тело 1 и 2 от взаимного проникновения в точке контакта. Оно только позволяет телам иметь "обратную"(outgoing) скорость в направлении нормали точки контакта. Время жизни контактного сочленения один шаг времени(one time step). Они создаются и уничтожаются в ответ на столкновения.

if (dGeomGetClass(o1) = dBoxClass) then

dJointAttach (c, 0, b2) //Присоединяем сочленение к телам

else

dJointAttach (c, b1, 0); //{Присоединяет сочленение к телам. Для того чтобы присоединить сочленение к одному телу надо установить body1 или body2 в ноль - ноль значит статическое окружение.}

end;

end;

{Далее ODE будет оперировать телами в соответствии с ранее установленными

параметрами контакта (mu, bounce и т.д.}

end;

Небольшое отклонение от темы. Очень часто необходимо определить какие тела столкнулись. Сделать это довольно просто, и, возможно, вы уже догадались. Итак, в процедуру nearCallback нужно вставить строку if ((b1 = odeBody1) and b2 = (odeBody2)) OR ((b1 = odeBody2) and b2 = (odeBody1)) then … И, наконец, последнее о геометрии. Это соединения (Joint). Как, например, в простейшем случае создается машина? Очень просто: для корпуса создается Box, для колес – четыре Sphere. Далее к этому Box’у по углам прикрепляются Sphere’ы. Легко сказать…

Сочлинения позволяют прикреплять одно тело к другому каким либо образом, а именно:

Ball--------– соединение «шарик в разъеме» Hinge------– соединение «сгибание» Hinge2---- – соединение «сгибание-2» Slider----- – «скользящее» соединение Universal--– «универсальное» соединение Fixed-------– «фиксированное» соединение

Contact-----– соединение «здесь сталкиваться нельзя». Подробно в этой статье я не буду их рассматривать. Но на примере Ball, Hinge,Slider, Hinge2 я покажу, как устроены cоединения.

Ball.

Сочленение шарик-в-разъеме показано на рисунке.

Сочленение шарик-в-разъеме.

dJointSetBallAnchor(const dJointID :Cardinal; x:dReal; y :dReal; z:dReal);

Устанавливает точку соединения(anchor) сочленения. Сочленение будет пытаться удержать два тела относительно этой точки. Входное значение дается в мировых координатах.

dJointGetBallAnchor(const dJointID :Cardinal; var result: dVector3);

Возвращает точку соединения сочленения в мировых координатах. Значение возвращается для тела 1. Если с сочленением все в порядке, то возвращенные значения для тел 1 и 2 будут совпадать.

dJointGetBallAnchor2(const dJointID :Cardinal; var result: dVector3);

Возвращает точку соединения сочленения в мировых координатах. Значение возвращается для тела 2. Вы можете думать о сочленении шарик-в-разъеме как о попытке возврата функциями dJointGetBallAnchor() и dJointGetBallAnchor2() одинакового результата. Если с сочленением все в порядке функция вернет такое же значение как и dJointGetBallAnchor( ) без ошибок в округлении. Функция dJointGetBallAnchor2( ) может быть использована вместе с функцией dJointGetBallAnchor( ) для определения того насколько части сочленения отдалились друг от друга.

Hinge.

Сочленение сгибание показано на рисунке.

Рисунок 5: Сочленение сгибание.

dJointSetHingeAnchor (const dJointID :Cardinal; const x :Single; const

y :Single; const z :Single);

dJointSetHingeAxis (const dJointID :Cardinal; const x :Single; const y

:Single; const z :Single);

Устанавливает параметры: точку сгиба(hinge anchor) и ось сгиба(hinge axis).

dJointGetHingeAnchor (const dJointID :Cardinal; var result: dVector3);

Получает точку сгиба сочленения в мировых координатах. Значение возвращается для первого тела. Если с сочленением все в порядке, то возвращенные значения для тел 1 и 2 будут совпадать.

dJointGetHingeAnchor2 (dJointID; var result: dVector3);

Полyчает точку сгиба сочленения в мировых координатах. Значение возвращается для второго тела. Если с сочленением все в порядке то возвращенное значение бyдет совпадать с значением возвращенным функцией dJointGetHingeAnchor(). Если нет, то значения будут немного отличаться. Это может быть использовано для определения того, насколько отдалились, друг от друга части сочленения.

dJointGetHingeAxis (const dJointID :Cardinal; var result: dVector3);

Получает ось сгиба.

dJointGetHingeAngle (const dJointID :Cardinal);

dJointGetHingeAngleRate (const dJointID :Cardinal);

Получает угол сгиба, зависящий от времени. Угол измеряется между двумя телами или между телом и статическим окружением. Значение угла лежит между -pi..pi.

Когда точка сгиба или ось определена, угол между текущим положением прикрепленных тел считается нулевым.

Slider.

Сочленение скольжение показано на рисунке.

Рисунок 6: Сочленение скольжение.

dJointSetSliderAxis (const dJointID :Cardinal; const x :Single; const y :Single; const z :Single);

Устанавливает ось скольжения(slider axis).

dJointGetSliderAxis (const dJointID :Cardinal; var result: dVector3);

Получает ось скольжения.

dJointGetSliderPosition (const dJointID :Cardinal);

dJointGetSliderPositionRate (const dJointID :Cardinal);

Полyчает позицию скольжения, зависящую от времени.

Когда ось определена, позиция скольжения, присоединенных тел, считается равной нулю.

Hinge2. Вот картинка, взятая мною из дока к ODE: Соединение Hinge2 специально создано для случаев, подобных колесу автомобиля с подвеской. Собственно, Body1 и Body2 – прикрепляемые тела (пусть будут корпус авто и колесо). Anchor – дословно «маячок», относительно которого прикреплены тела. Положение тел в пространстве изменяется относительно осей Axis1 и Axis2. В данном соединении ось Axis1 может поворачиваться вокруг своей оси и, подобно рессоре, сжиматься/разжиматься. Ось Axis2 может только вращаться вокруг своей оси. Кроме того, каждая из осей Соединение имеет множество параметров:

Параметр

Описание

dParamBounce

упругость остановок;

dParamLoStop

нижний предел угла или позиции (например, для Axis1 – это предельный угол поворота,

т.е. на сколько может поворачивать колесо)

dParamHiStop

верхний предел угла или позиции

dParamVel

желаемая скорость двигателя (например, для Axis2 – это желаемая скорость вращения)

dParamFMax

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

dParamFudgeFactor

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

dParamCFM, dParamStopERP, dParamStopCFM, dParamSuspensionERP, dParamSuspensionCFM

параметры уменьшения ошибок, здесь описываться не будут

Теперь о коде. Создание группы Соединений: Var Joint: TdJointID; Joint := dJointCreateXXX (World: pdxWorld; GroupID: TdJointGroupID;); где XXX – имя типа сочленения (Hinge и т.д.), World – ясно, GroupID – необязательный идентификатор группы Соединений или ноль (см. док по ODE). Далее необходимо указать, какие тела соединяются друг с другом: dJointAttach (joint: TdJointID; body1,body2: pdxBody); (здесь вроде все понятно). Теперь определяем параметры Соединений: // Устанавливает маячок (не забудьте, что в ODE ось z направлена вверх) JointSetHinge2Anchor(joint: TdJointID; x, y, z: Single); // Устанавливает направление осей JointSetHinge2Axis1(joint: TdJointID; x, y, z: Single); JointSetHinge2Axis2(joint: TdJointID; x, y, z: Single); // Устанавливает параметры Соединения JointSetHinge2Param(joint: TdJointID; parameter: Integer; Value: Single); И, наконец, в Cadencer’е необходимо каждый раз делать шаг (т.е. перерасчет) физики, для чего есть аж три функции: // Медленная, но самая точная dWorldStep (world: pdxWorld; deltaTime: Single); // Значительно быстрее + меньший расход памяти, но страдает точность dWorldQuickStep (world: pdxWorld; deltaTime: Single); // Почти не отличается от QuickStep и, возможно, будет заменена ей в будущем dWorldStepFast (world: pdxWorld; deltaTime: Single); Для игр больше всего подходит QuickStep, для демок – WorldStep. Предпоследнее – об уничтожении объектов. Все объекты в конце программы должны быть уничтожены вызовом dXXXDestroy(param); где XXX – тип объекта (Body, World, Space и т.д.), param – уничтожаемый объект; а в самом конце – вызов dCloseODE, освобождающий недоступные пользователю ресурсы. И последнее – общий принцип организации программ с ODE: 1) создание Мира и настройка его параметров (типа гравитации и т.д.) 2) создание Тел и настройка их параметров (масса, плотность и т.д.) 3) создание Геометрии и прикрепление ее к телам 4) создание Соединений и настройка их параметров (маячки, оси и т.д.) 5) цикл проверка столкновений шаг симуляции 6) освобождение ресурсов Ссылки: http://ode.org/doc/russian/ - официальная документация по ODE на русском языке. Она, не буду таить, сильно устарела. Но основы остались те же. http://www.glscene.ru/download.php?view.233 - отличный пример на чистом ODE, демонстрирующий все, описанное в статье.

http://www.glscene.ru/download.php?view.417пожалуй, самый простой и понятный пример на чистом ODE. Снабжён отличными комментариями!

http://www.glscene.ru/download.php?view.382- неплохой пример на чистом ODE для новичков.

http://www.glscene.ru/download.php?view.406 – немного корявый пример реализации автомобиля на чистом ODE.

http://opende.sourceforge.net/wiki/index.php/Main_Page - очень хороший англоязычный обновляемый мануал по ODE.