Курсы лаборатории компьютерной графики
Обязательный полугодовой курс ВМиК МГУ
     

Использование физической библиотеки Tokamak.

Автор урока:
Кристина Каштанова

Назад

Подключение нужных библиотек.

Скачать последнюю версию бесплатного физического движка Tokamak можно здесь

Нужно добавить папки в MSVC:

Visual Studio 6:
  • Tools -> Options в меню MSVC
  • Выбрать вкладку Directories
  • Убедиться в том, что выбрано "Include files" в "Show directories for"
  • Дважды щёлкните по пустому месту в этом окне, его можно будет редактировать
  • Нажмите на "…" кнопку, чтобы выбрать место, куда вы установили SDK. Здесь нужно выбрать папку Include SDK
  • Нажмите OK
Visual Studio .Net:
  • Tools -> Options
  • Войти во вкладку Projects
  • Перейти на VC++ Directories
  • Убедиться в том, что выбрано "Include files" в "Show directories for"
  • Дважды щёлкните по пустому месту в этом окне, его можно будет редактировать
  • Нажмите на "…" кнопку, чтобы выбрать место, куда вы установили SDK. Здесь нужно выбрать папку Include SDK
  • Нажмите OK
То же самое нужно проделать для библиотек (выбрав в "Show directories for" "Libraries").

Введение.

Движок различает два типа объектов - движущиеся (rigid) и статические (animated). Для первых движком просчитываются все законы физики (которые будут указаны), вторые же рисуются и движутся только благодаря пользователю (но влияют на динамические объекты). В движке много различных параметров, которые влияют на то, как он будет работать. Есть главный класс движка (neSimulator), он используется для доступа ко всем частям движка и обновлению текущей позиции всех объектов.

Инициализация.

Включаем заголовочный файл и подключаем библиотеку:

#include <tokamak.h>
#pragma comment(lib, "tokamak.lib")

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


#define CUBES_NUM         5
#define CUBES_SIZE        1.0f
#define FLOOR_SIZE        10.0f


neSimulator* pSim = 0;
neRigidBody* pCubes[CUBES_NUM];
neAnimatedBody* pFloor = 0;

Сделаем функцию инициализации движка. В ней создадим сам движок, создадим объекты движка, зададим их положения, массы, установим силы (в данном примере, одну силу - силу гравитации).


void  InitPhysics()
{

   //описывает геометрию любого тела: куб, шар, цилиндр или их объединения
   neGeometry *geom;
   //размер куба (длина, ширина и высота)
   neV3 boxSize1;
   //вектор силы гравитации
   neV3 gravity;
   //позиция - координаты центра тела 
   neV3 pos;
   //масса
   f32 mass;
   //структура, необходимая для инициализации движка 
   neSimulatorSizeInfo sizeInfo;

   
   //
   //заполняем структуру
   sizeInfo.rigidBodiesCount = CUBES_NUM;
   sizeInfo.animatedBodiesCount = 1;
   s32 totalBody = sizeInfo.rigidBodiesCount + sizeInfo.animatedBodiesCount;
   sizeInfo.geometriesCount = totalBody;
   //максимальное допустимое количество столкновений
   sizeInfo.overlappedPairsCount = totalBody * (totalBody - 1) / 2;
   sizeInfo.rigidParticleCount = 0;
   sizeInfo.constraintsCount = 0;
   sizeInfo.terrainNodesStartCount = 0;
   //установка значения вектора гравитации - сила притяжения направлена вниз
   //и имеет величину 10 Ньютон
   gravity.Set(0.0f, -10.0f, 0.0f);
   
   //создаём движок - вызов статической функции
   pSim = neSimulator::CreateSimulator(sizeInfo, NULL, &gravity);
   for (int i = 0; i < CUBES_NUM; i++)
   {
   
   	 //создаём движущееся тело 
      pCubes[i] = pSim->CreateRigidBody();
	 //нужно задать геометрию тела, для начала добавим такое свойство как геометрия
      geom = pCubes[i]->AddGeometry();
	 //устанавливаем размеры куба
	  boxSize1.Set(CUBES_SIZE, CUBES_SIZE, CUBES_SIZE);
	 //нужно установить только что созданную геометрию
	  geom->SetBoxSize(boxSize1[0], boxSize1[1], boxSize1[2]);
     //изменения вступают в действие 
	  pCubes[i]->UpdateBoundingInfo();
      mass = 1.0f;
	 //инертность тела - заставляет после соударения закручиваться
	 //для кубов  есть класс neBoxInertiaTensor 
	 //который сам вычисляет инертность по размеру и массе куба
      pCubes[i]->SetInertiaTensor(neBoxInertiaTensor(boxSize1[0],
         boxSize1[1],
         boxSize1[2],
         mass));
	 //устанавливаем массу
	  pCubes[i]->SetMass(mass);
	 //задаём позицию 
	  pos.Set((float)(rand()%10) / 100, 4.0f + i * CUBES_SIZE,
         (float)(rand()%10) / 100);
     //устанавливаем позицию
	   
	  pCubes[i]->SetPos(pos);
   }
  //аналогичным образом создаём статический объект
   pFloor = pSim->CreateAnimatedBody();
   geom = pFloor->AddGeometry();
   boxSize1.Set(FLOOR_SIZE, 0.2f, FLOOR_SIZE);
   geom->SetBoxSize(boxSize1[0],boxSize1[1],boxSize1[2]);
   pFloor->UpdateBoundingInfo();
   pos.Set(0.0f, -3.0f, 0.0f);
   pFloor->SetPos(pos);
}

Небольшие пояснения по инициализации.

//максимальное допустимое количество столкновений sizeInfo.overlappedPairsCount = totalBody * (totalBody - 1) / 2;

В нашем примере все тела могут столкнуться одновременно, поэтому такое значение. Если бы тела, например, находились в космическом пространстве, на большом расстоянии, то было бы естественно поставить это значение достаточно маленьким. В качестве статического объекта мы хотим задать плоскость, но плоскости не поддерживаются движком (что и понятно, так как это не тела - не имеют объёма), поэтому аналогично движущимся телам делаем параллелепипед, но с небольшой высотой.

Обновление положения тел.

Чтобы передвинуть тела, в движке нужно вызвать метод Advance, у которого единственным параметром является время в секундах с прошлого вызова функции. Для определения этого параметра напишем функцию:

float GetElapsedTime()
{
    static int oldTime = GetTickCount();
    int newTime = GetTickCount();
    float result = (newTime - oldTime) / 1000.0f;
    oldTime = newTime;
    return result;
}

По рекомендациям разработчиков движка нужно ограничиться тем, что интервал времени, на который мы хотим сдвинуть все объекты не должен отклоняться более, чем на 20% от предыдущего. И не должен превышать 1/45-ю секунды.

void UpdateObjects()
{

    static float oldElapsed;
    float newElapsed = GetElapsedTime();
    if (oldElapsed != 0.0f)
    {
	    //проверяем отклонение на 20% выше и ниже (120% и   80%)
        if (newElapsed < 0.8f * oldElapsed)
            newElapsed = 0.8f * oldElapsed;
        if (newElapsed > 1.2f * oldElapsed)
            newElapsed = 1.2f * oldElapsed;
        //на 1/45-ю секунды
		if (newElapsed > 1.0f/45.0f)
            newElapsed = 1.0f/45.0f;
    }
    oldElapsed = newElapsed;
    pSim->Advance(newElapsed);
}

Отрисовка.

Задать положение можно разными способами. В OpenGL телу задаются некоторые координаты и чтобы повернуть, либо сдвинуть тело, нужно координаты умножить на соответствующие матрицы поворота и сдвига. То есть, чтобы повернуть куб вокруг осей на a, b, c, с центром в x, y, z нужно сделать следующее:

//инструкции выполняются снизу вверх из-за способа перемножения матриц
glTranslated(x, y, z);
glRotated(c, 0.0, 0.0, 1.0);
glRotated(b, 0.0, 1.0, 0.0);
glRotated(a, 1.0, 0.0, 0.0);
//рисуем куб, как будто он в центре координат и расположен
//параллельно осям 
//...

При таком методе рисования нам не нужны сами координаты сдвинутого объекта, нужны только матрица поворота и вектор сдвига. Метод GetTransform любого тела движка возвращает как раз матрицу поворота и вектор сдвига, упакованные в одну структуру neT3.

void DrawCubes()
{
	//хранит значение, возвращаемое GetTransform кубика
    neT3 t;
	//матрица 4х4 для OpenGL, включает в себя вектор сдвига и матрицу поворота
    float matrix[16];
	//повторяем процедуру для каждого кубика
    for (int i = 0; i < CUBES_NUM; i++)
    {
	//получаем данные для кубика
        t = pCubes[i]->GetTransform();
	//переводим их в данные, пригодные для использования в  OpenGL 
	//копируем матрицу поворота
        matrix[0] = t.rot[0][0];
        matrix[4] = t.rot[0][1];
        matrix[8] = t.rot[0][2];
        matrix[1] = t.rot[1][0];
        matrix[5] = t.rot[1][1];
        matrix[9] = t.rot[1][2];
        matrix[2] = t.rot[2][0];
        matrix[6] = t.rot[2][1];
        matrix[10] = t.rot[2][2];
    //копируем вектор сдвига
		matrix[12] = t.pos[0];
        matrix[13] = t.pos[1];
        matrix[14] = t.pos[2];
	//остальное
        matrix[3] = 0.0f;
        matrix[7] = 0.0f;
        matrix[11] = 0.0f;
        matrix[15] = 1.0f;
	//теперь рисуем
	//сохраняем матрицу преобразований вершин OpenGL,
	//чтобы не влиять на другие кубики и пол
        glPushMatrix();
	//умножаем текущую матрицу на полученную
        glMultMatrixf(matrix);
	//Рисуем куб в центре координат заданных размеров
        Cube(CUBES_SIZE, CUBES_SIZE, CUBES_SIZE);
	//Возвращаем матрицу в прежний вид
        glPopMatrix();
    }
}

Теперь аналогичным образом рисуем пол.


void DrawFloor()
{
    neT3 t;
    float matrix[16];
    t = pFloor->GetTransform();
    matrix[0] = t.rot[0][0];
    matrix[4] = t.rot[0][1];
    matrix[8] = t.rot[0][2];
    matrix[1] = t.rot[1][0];
    matrix[5] = t.rot[1][1];
    matrix[9] = t.rot[1][2];
    matrix[2] = t.rot[2][0];
    matrix[6] = t.rot[2][1];
    matrix[10] = t.rot[2][2];
    matrix[12] = t.pos[0];
    matrix[13] = t.pos[1];
    matrix[14] = t.pos[2];
    matrix[3] = 0.0f;
    matrix[7] = 0.0f;
    matrix[11] = 0.0f;
    matrix[15] = 1.0f;
    glPushMatrix();
    glMultMatrixf(matrix);
    Cube(FLOOR_SIZE, 0.2f, FLOOR_SIZE);
    glPopMatrix();
}

Сама функция рисования будет выглядеть так:


void DrawOpenGL()
{
    //обновляем положение тел
    UpdateObjects();
	//чистим экран и сбрасываем матрицу преобразований
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
	//Отодвигаем сцену от камеры, чтобы все было видно
    glTranslated(0.0, 0.0, -5.0);
    //Рисуем объекты сцены, уже обновленные
	DrawCubes();
    DrawFloor();
	//бновляем содержимое экрана
    SwapBuffers();
}

Отрисовка куба

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


void Cube(float length, float width, float height)
{
    length /= 2;
    width /= 2;
    height /= 2;
    GLfloat v0[] = { -length, -width,  height };
    GLfloat v1[] = {  length, -width,  height };
    GLfloat v2[] = {  length,  width,  height };
    GLfloat v3[] = { -length,  width,  height };
    GLfloat v4[] = { -length, -width, -height };
    GLfloat v5[] = {  length, -width, -height };
    GLfloat v6[] = {  length,  width, -height };
    GLfloat v7[] = { -length,  width, -height };
    static GLubyte red[]    = { 255,   0,   0, 255 };
    static GLubyte green[]  = {   0, 255,   0, 255 };
    static GLubyte blue[]   = {   0,   0, 255, 255 };
    static GLubyte white[]  = { 255, 255, 255, 255 };
    static GLubyte yellow[] = {   0, 255, 255, 255 };
    static GLubyte black[]  = {   0,   0,   0, 255 };
    static GLubyte orange[] = { 255, 255,   0, 255 };
    static GLubyte purple[] = { 255,   0, 255,   0 };

    glBegin(GL_TRIANGLES);

    glColor4ubv(red);
    glVertex3fv(v0);
    glColor4ubv(green);
    glVertex3fv(v1);
    glColor4ubv(blue);
    glVertex3fv(v2);

    glColor4ubv(red);
    glVertex3fv(v0);
    glColor4ubv(blue);
    glVertex3fv(v2);
    glColor4ubv(white);
    glVertex3fv(v3);

    glColor4ubv(green);
    glVertex3fv(v1);
    glColor4ubv(black);
    glVertex3fv(v5);
    glColor4ubv(orange);
    glVertex3fv(v6);

    glColor4ubv(green);
    glVertex3fv(v1);
    glColor4ubv(orange);
    glVertex3fv(v6);
    glColor4ubv(blue);
    glVertex3fv(v2);

    glColor4ubv(black);
    glVertex3fv(v5);
    glColor4ubv(yellow);
    glVertex3fv(v4);
    glColor4ubv(purple);
    glVertex3fv(v7);

    glColor4ubv(black);
    glVertex3fv(v5);
    glColor4ubv(purple);
    glVertex3fv(v7);
    glColor4ubv(orange);
    glVertex3fv(v6);

    glColor4ubv(yellow);
    glVertex3fv(v4);
    glColor4ubv(red);
    glVertex3fv(v0);
    glColor4ubv(white);
    glVertex3fv(v3);

    glColor4ubv(yellow);
    glVertex3fv(v4);
    glColor4ubv(white);
    glVertex3fv(v3);
    glColor4ubv(purple);
    glVertex3fv(v7);

    glColor4ubv(white);
    glVertex3fv(v3);
    glColor4ubv(blue);
    glVertex3fv(v2);
    glColor4ubv(orange);
    glVertex3fv(v6);

    glColor4ubv(white);
    glVertex3fv(v3);
    glColor4ubv(orange);
    glVertex3fv(v6);
    glColor4ubv(purple);
    glVertex3fv(v7);

    glColor4ubv(green);
    glVertex3fv(v1);
    glColor4ubv(red);
    glVertex3fv(v0);
    glColor4ubv(yellow);
    glVertex3fv(v4);

    glColor4ubv(green);
    glVertex3fv(v1);
    glColor4ubv(yellow);
    glVertex3fv(v4);
    glColor4ubv(black);
    glVertex3fv(v5);

    glEnd();
}

Примечание.

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

Главная | О курсе | Лекции | Библиотека | Задания | Оценки | FAQs | Форум
  (с) Лаборатория компьютерной графики, 1997-2005
Дизайн: Алексей Игнатенко