GLScene. За кулисами Open Dynamic Engine

Всем большой и пламенный привет! Давненько мы с вами не разбирались с различными вкусностями в GLScene. Сегодня вернёмся к теме, которую я уже затрагивал – физика. В прошлый раз я рассказал про физический движок DCE (см. «МБ» №39’2007). А сегодня…

Как мне кажется, перемены (не путать с переменными в программе и переменами в школе) программисты переживают максимально остро. Зачем переходить на что-то новое, когда есть такое милое и удобное старое? И пусть это старое сто раз глючило, вызывало кучу проблем и неизлечимый диатез – всё равно программист цепляется за него всеми зубами, очками и клавиатурой. Такой случай у меня произошёл с DCE и ODE. Вот знал, что DCE – это для новичков, прошлый век, абсолютно примитивная физика, но ничего не мог с собой поделать. То ли дело ODE: у всех на слуху, отличная физика и производительность… но такой он чужой, этот ODE!

Готовим Rag-doll
Сторонники ODE делятся на два лагеря – «рантаймеры» и «дизайнтаймеры». Вторые добавляют физику к объектам ещё во время создания проекта в стадии «дизайн». Для этого используются две вещи: GLODEManager и свойство Behaviour, необходимого объекта. С DCE была похожая ситуация; соответственно, если вы станете дизайнтаймером, то безболезненно перейдёте с DCE на ODE. Однако не спешите этого делать. Дело в том, что GLODEManager не способен работать с Trimesh (трёхмерная сетка). Точнее, эта возможность имеется, но никто не может привести её в нормальное состояние. А попытавшись это сделать, вы автоматически перейдёте в ранг «рантаймер».
Эти кодеры (и я вместе с ними) отрицают GLODEManager в корне. А без GLODEManager выход только один: создавать физический мир одним кодом. Возможности при этом открываются буквально безграничные. Дело в том, что GLODEManager – компонент, созданный специально для GLScene, используя который, мы попросту теряем половину возможностей ODE (ну не перенесли его на GLScene полностью). А вообще, физический движок ODE может интегрироваться в любую среду разработки – была бы только у среды поддержка DLL.

Плавно переходим к установке ODE в Delphi. Заходим в родной каталог GLScene\Delphi7 (или в папку с названием вашей среды разработки; я рассматриваю установку на примере Delphi 7). Находим файл GLS_ODE7.dpk и запускаем. Далее установка не очень отличается от установки самой GLScene – те же DCU- и INC-файлы. Ещё нужно запустить скриптик GLScene\Source\PhysicsAPIs\InstalDLL.bat. Всё, теперь движок ODE прочно поселился на вашем компьютере.

ODE + программист = ...
В Delphi на панели компонентов во вкладке GLScene появились компоненты, связанные с ODE. Изучение их после знакомства с DCE пройдёт у вас безболезненно. В крайнем случае, можно посмотреть стандартные демки из папки GLScene\Demos\physics\. Я расскажу вам о создании мира кодом.
Для начала добавим ODEImport, ODEGL, VectorGeometry в раздел Uses. Теперь нужно в разделе private указать следующие переменные:

private
 World: PdxWorld;
 Space: PdxSpace;
 ContactGroup: TdJointGroupID;
 GeomList: TGeomList;
 PhysicsTime: single;

Для процедуры Form1.Create добавляем локальные переменные:

var
 map: PdxGeom;
 Vertices: PdVector3Array;
 Indices: PdIntegerArray;

В самой процедуре пишем:

// Создаём физический мир
World := dWorldCreate();
// Создаём физическое пространство
Space := dHashSpaceCreate(nil);
// Максимальное количество «контактов»
ContactGroup:=dJointGroupCreate(1000000);
// Устанавливаем гравитацию по XYZ.
// Обратите внимание, что в ODE
// на месте оси Z стоит ось Y.
dWorldSetGravity (World,0,0,-9);
// указываем точность обсчёта физики
dWorldSetQuickStepNumIterations(world, 3);

Пример того, как добавить физику к подгружаемому 3D-объекту:

 FreeForm1.LoadFromFile('tmpQuark\maps\sanom.3ds');
 map:=CreateTriMeshFromBaseMesh(FreeForm1, Space, Vertices, Indices);
 map.data:=FreeForm1;

В конце процедуры допишите:

GeomList:=TGeomList.Create;

Эта строчка создаст список всех физических тел. Однако не нужно писать её строчку в самом конце процедуры. Лично у меня был такой глюк: в Form1.Create прописал добавление динамических примитивов, а потом уже создал список физических объектов. Однако компилятору это не понравилось. Но когда поменял блоки кода местами – всё заработало.

Терминатор CALL back!
Теперь обратимся к основному обработчику физики в проекте. Это процедура nearCallback, отвечающая за столкновение двух физических объектов. Полный её код – в листинге 1.

procedure nearCallback (data: pointer; o1, o2: PdxGeom); cdecl;
const
  cCOL_MAX = 12;
var
  i: integer;
  b1, b2: PdxBody;
  numc: integer;
  contact: array[0..cCOL_MAX-1] of TdContact;
  c: TdJointID;
begin
  b1:= dGeomGetBody(o1);
  b2:= dGeomGetBody(o2);
  if (assigned(b1) and assigned(b2) and (dAreConnected (b1,b2)0))
   then exit;
  for i:=0 to cCOL_MAX-1 do
   begin
    contact[i].surface.mode := dContactBounce;
    // Mu – это всем известный коээфициент трения ”Мю”.
    // Bounce – свойство, отвечающее за упругость объекта
    contact[i].surface.mu := 10e9;
    contact[i].surface.mu2 := 0;
    contact[i].surface.bounce := 0.5;
    contact[i].surface.bounce_vel := 0.1;
   end;
   numc:=dCollide (o1,o2,cCOL_MAX,contact[0].geom,sizeof(TdContact));
   if (numc>0) then
    begin
     for i:=0 to numc-1 do
      begin
       c:=dJointCreateContact (Form1.world,Form1.contactgroup,@contact[i]);
       dJointAttach (c,b1,b2);
      end;
    end;
end;

Уф-ф-ф! Тяжеловато для понимания, правда? Но труд вознаграждается результатом – это, собственно, всё, что нам нужно для расчёта физики. Обратите внимание, что процедуру nearCallback не нужно указывать в списке процедур проекта – дело в том, что она уже названа в модуле ODE.

Огласите весь список, пожалуйста!
Если вы уже смогли подгрузить 3D-объект во FreeForm, начинённый физической оболочкой от ODE, самое время испытать движок в действии. Попробуем добавить на сцену несколько примитивов, чтобы посмотреть, как они будут взаимодействовать с нашей картой. Я создал свою процедуру, код которой приведён в листинге 2.

procedure TForm1.CreateObject;
var
  R: TdMatrix3;
  k: integer;
  m: TdMass;
  Sides: TdVector3;
  Geom: PdxGeom;
begin
  for k:=0 to 2 do sides[k]:= 10;
  // указываем массу
  dMassSetBox (m,2.5,sides[0],sides[1],sides[2]);
  // создаём оболочку типа «куб»
  geom:= dCreateBox (space,10,10,10);
  // добавьте в список глобальных переменных Object: pdxBody;
  Object:= dBodyCreate(World);
  // позиция
  dBodySetPosition (Object, 0, 0, 0);
  // добавляем в список физических объектов
  GeomList.Add(Geom);
  // совмещаем объект с физической оболочкой
  dGeomSetBody (Geom,Object);
  dBodySetMass (Object, @m);
  // MainMap – это dummycube, в который я добавляю все динамические объекты
  Geom.Data:=Form1.MainMap.AddNewChild(TGLCube);
  with TGLCube(Geom.Data) do
   begin
    CubeWidth:=sides[0];
    CubeHeight:=sides[1];
    CubeDepth:=sides[2];
    Material.FrontProperties.Diffuse.AsWinColor:=clLime;
   end;
end;

Теперь в GLCadencer.Progress пишем:

if MainMap.Count>100 then CreateObject;

Так мы добавили на сцену сто твёрдых кубиков. В этой же процедуре пишем:

while PhysicsTime<newTime do
 begin
 // проверяем объекты на столкновения
 dSpaceCollide(space,nil,nearCallback);
 // это процедура «шага» обработки физики
 dWorldQuickStep(world, 0.01);
 PhysicsTime:=PhysicsTime + 0.01;
 // удаляем все «устаревшие контакты»
 dJointGroupEmpty(contactgroup);
end;
// отрисовываем заново все объекты
RenderGeomList(GeomList);

Запускаем программу и наслаждаемся результатом. Мой вариант исполнения можно посмотреть на рисунке.

Учебник по физике
Теперь пришло время поговорить о движении физических тел в пространстве. Вот список процедур:

// указываем позицию
dBodySetPosition (dBodyID, dReal x, dReal y, dReal z);
// задаём вектор линейной скорости
dBodySetLinearVel (dBodyID, dReal x, dReal y, dReal z);
// вектор угловой скорости
dBodySetAngularVel (dBodyID, dReal x, dReal y, dReal z); 

Кроме того, можно использовать функции dBodyAddForce, dBodyAddRelForce, dBodyAddForceAtPos, dBodyAddForceAtRelPos, dBodyAddRelForceAtPos, dBodyAddRelForceAtRelPos.

Функции ...RelForce и ...RelTorque принимают в качестве параметров векторы в локальной системе координат этого тела. Функции ...ForceAtPos и ...ForceAtRelPos принимают вектор с позицией точки приложения силы (в глобальной или локальной системе координат соответственно). Все другие функции прилагают силу к центру масс.

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


Рекомендуем почитать: