Назад
Подключение нужных библиотек.
Скачать последнюю версию бесплатного физического движка 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 на момент написания урока находится в стадии разработки, то допускается
некоторая неточность в обработке столкновений (как движущихся тел между собой, так и со статическими
телами) а именно - небольшое проваливание частей тел. Так как это является проблемой библиотеки, то
при выполнении задания не следует обращать внимания на него (артифакт будет устранён в следующих версиях по
обещанию авторов).
|