Бесплатная реклама

Автор: Александр Залога

Опубликовано: 03.04.2007

Изменено: 03.04.2007

Постоянная ссылка

Комментарии [0]

Введение в физический движок AGEIA PhysX





Страницы: 1 2 3

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

Мы создадим объект отображения информации (HUD - heads-up-display object), который будет показывать, запущена ли сцена в режиме hardware, поставлена ли сцена на паузу, и дополнительную информацию, необходимую при обучении.

// HUD globals
HUD hud;
...
// Simulation globals
...
bool bHardwareScene = false;
bool bPause = false;
...
void InitNx()
{
    ...
    // Initialize HUD
    bHardwareScene = (gScene->getSimType() == NX_SIMULATION_HW);
    hud.Update(bHardwareScene, bPause, "");
    ...
}

Класс HUD и представляющие его переменные и функции описаны в файлах HUD.cpp и HUD.h.

9. Вычисление времени

Далее мы вызываем функцию UpdateTime().

UpdateTime.h

NxReal UpdateTime()
{
    NxReal deltaTime;
#ifndef LINUX
    static __int64 gTime,gLastTime;
    __int64 freq;
    QueryPerformanceCounter((LARGE_INTEGER *)&gTime);  // Get current count
    QueryPerformanceFrequency((LARGE_INTEGER *)&freq); // Get processor freq
    deltaTime = (double)(gTime - gLastTime)/(double)freq;
    gLastTime = gTime;
#else
    struct timeval tv;
    static struct timeval lasttv = { 0 , 0 };
    if (lasttv.tv_usec == 0 && lasttv.tv_sec == 0)
        gettimeofday(&lasttv, NULL);
    gettimeofday(&tv, NULL);
    deltaTime = (tv.tv_usec - lasttv.tv_usec)/1000000.f + (tv.tv_sec - lasttv.tv_sec);
    lasttv = tv;
#endif
    return deltaTime;
}

Lesson101.h

void InitNx()
{
    ...
    // Get the current time
    UpdateTime();
    ...
}

В переменную gTime записывается текущее время. Каждый кадр мы вызываем UpdateTime(), чтобы получить deltaTime – количество времени, которое прошло с момента последней отрисовки (rendering) сцены, т.е. количество времени, на которое нужно продвинуть симуляцию для рисования текущей сцены.

10. Запуск первого кадра симуляции

В завершении мы вызываем функцию StartPhysics() для запуска первого кадра симуляции (процесса моделирования).

void InitNx()
{
    ...
    // Start the first frame of the simulation
    if (gScene)  StartPhysics();
    ...
}

void StartPhysics()
{
    // Update the time step
    gDeltaTime = UpdateTime();

    // Start collision and dynamics for delta time since the last frame
    gScene->simulate(gDeltaTime);
    gScene->flushStream();
}

11. Получение результатов моделирования

Мы инициализировали SDK и создали нашу сцену с помощью InitNx(). Теперь мы запускаем главный цикл рисования glutMainLoop() из функции main().

int main(int argc, char** argv)
{
    PrintControls();
    InitGlut(argc, argv);
    InitNx();
    glutMainLoop();
    ReleaseNx();
    return 0;
}

Выполнение программы находится в цикле glutMainLoop(), пока пользователь не завершит симуляцию. После отрисовки сцены, вызывается функция RenderCallback(). Вызывая её, мы подготавливаем следующую сцену. В начале функции RenderCallback() мы вызываем NxScene::fetchResults() (fetch – добиваться, получать) из GetPhysicsResults(), чтобы получить результаты нашей физической симуляции с момента последнего вызова NxScene::simulate() из StartPhysics().

void RenderCallback()
{
    ...
    if (gScene && !bPause)
    {
        GetPhysicsResults();
        ProcessInputs();
        StartPhysics();
    }
...
}

void GetPhysicsResults()
{
    // Get results from gScene->simulate(deltaTime)
    while (!gScene->fetchResults(NX_RIGID_BODY_FINISHED, false));
}

Мы вызываем NxScene::fetchResults(NX_RIGID_BODY_FINISHED, false) чтобы обеспечить закрытие симуляции, запущенной последним вызовом функции NxScene::simulate(). Параметр "NX_RIGID_BODY_FINISHED" определяет, хотим ли мы завершить симуляцию твердых тел на этом отрезке времени. Параметр "false" определяет, что этот вызов неблокируемый. Мы не хотим блокировать ожидание завершения симуляции твердого тела. (т.е. мы хотим дождаться её завершения). Если вместо того мы поставим здесь "true":

void GetPhysicsResults()
{
    // Get results from gScene->simulate(deltaTime)
    while (!gScene->fetchResults(NX_RIGID_BODY_FINISHED, true));
}

... то NxScene::fetchResults() не захочет завершаться полностью, пока не будет завершена симуляция. С нашим вызовом (используя "false"), мы непрерывно проверяем, завершена ли симуляция и не выведена ли из цикла, пока функция не вернула "true". Мы можем использовать это пространство для запуска дополнительного кода в случае, если физика на этом кадре не была завершена.

void GetPhysicsResults()
{
    // Получить результаты от gScene->simulate(deltaTime)
    while (!gScene->fetchResults(NX_RIGID_BODY_FINISHED, false))
    {
        // Выполнить здесь дополнительный код, например для рендеринга, ИИ и т.д.
        // пока ждем окончания симуляции "rigid body"
        ...
        ...
    }
}

12. Обработка внешних воздействий

Затем мы запускаем ProcessInputs(), чтобы собрать все физические входы новой сцены и чтобы вызвать некоторые функции для новой сцены. Здесь мы применяем силы к ящику, изменяем позицию ящика и вызываем другие функции, а именно рисование отладочной информации на следующем шаге симуляции.

void RenderCallback()
{
    ...
    if (gScene && !bPause)
    {
        GetPhysicsResults();
        ProcessInputs();
        StartPhysics();
    }
    ...
}

void ProcessInputs()
{
    ProcessForceKeys();

    // Show debug wireframes
    if (bDebugWireframeMode)
    {
        if (gScene)  gDebugRenderer.renderData(*gScene->getDebugRenderable());
    }
}
12.1. Применение сил к ящику

Сначала вызываем функцию ProcessForceKeys(), где используется функция ApplyForceToActor() применительно к нашему ящику.

// Force globals
NxVec3 gForceVec(0,0,0);
NxReal gForceStrength = 20000;
bool bForceMode = true;
...
// Keyboard globals
#define MAX_KEYS 256
bool gKeys[MAX_KEYS];
...
void ProcessForceKeys()
{
    // Process force keys
    for (int i = 0; i < MAX_KEYS; i++)
    {     
        if (!gKeys[i])  { continue; }

        switch (i)
        {
            ...
            // Force controls
            ...
            case 'j': 
                { gForceVec = ApplyForceToActor(box, NxVec3(1,0,0), gForceStrength); break; }
            ...
        }
    }
}
...

void ProcessInputs()
{
    ProcessForceKeys();
    ...
}

// ApplyForceToActor() calls NxActor::addForce() to add forces to the box 
// through keyboard input.

NxVec3 ApplyForceToActor(NxActor* actor, const NxVec3& forceDir, const NxReal forceStrength)
{
    NxVec3 forceVec = forceStrength*forceDir*gDeltaTime;
    actor->addForce(forceVec);
    return forceVec;
}

Всё время, пока нажата клавиша "j", мы вызываем box->addForce(NxVec3(gForceStrength,0,0)) и сила gForceStrength = 20000 постоянно воздействует на ящик в положительном направлении оси X. Заметьте, что когда симуляция начинается, вы смотрите на сцену сзади, в положительном направлении оси Z (она отмечена синей стрелкой). Ось Y направлена вверх (зеленая стрелка) и ось X направлена влево (красная стрелка). Все оси в SDK устанавливаются и рисуются таким образом. "j" сейчас сообщает силу, толкающую ящик влево. Силы показываются желтыми стрелками. При использовании кнопок "u,m,I,j,k,l" силы имеют постоянное значение и прикладываются к центру ящика, и всегда вдоль одной из глобальных осей (X, Y или Z).

12.2. Изменение позиции ящика

Затем в функции ProcessKeys() мы вызываем NxActor::setGlobalPosition(), чтобы изменить положеие ящика.

// Keyboard globals
#define MAX_KEYS 256
bool gKeys[MAX_KEYS];
...
void ProcessKeys()
{
    // Process keys
    for (int i = 0; i < MAX_KEYS; i++)
    {     
        if (!gKeys[i])  { continue; }

        switch (i)
        {
            ...
            // Return box to (0,5,0)
            case 't': { box->setGlobalPosition(NxVec3(0,5,0)); break; }
            ...
        }
    }
}

Каждый раз, когда мы нажимаем "t", мы запускаем NxActor::setGlobalPosition(NxVec3(0,5,0)) для нашего ящика-актера. Таким образом, мы помещаем его в позицию на 5 единиц выше начала координат.

Как и с применением сил, нам нужно сделать это между вызовом NxScene::fetchResults() в GetPhysicsResults() и вызовом NxScene::simulate() в StartPhysics(). Мы должны ввести это в физическую сцену во время её подготовки, пока сцена ещё не запущена на вычисление железом.

12.3. Отладка объектов

После обработки возможных вызовов по добавлению сил и изменению позиции ящика, мы вызываем такой код:

void ProcessInputs()
{
    ...
    // Show debug wireframes
    if (bDebugWireframeMode)
    {
        if (gScene)  gDebugRenderer.renderData(*gScene->getDebugRenderable());
    }
}

Нажатие "b" в демонстрации включает или выключает режим показа каркасов. Вызов gDebugRenderer.renderData() рисует каркасы на сцене по тем параметрам, которые мы установили в InitNx(), чтобы мы видели сталкивающиеся фигуры и оси актеров на сцене. Вы можете посмотреть на код функции DebugRenderer::renderData() в файле DebugRenderer.cpp.

void DebugRenderer::renderData(const NxDebugRenderable& data) const
{
    glLineWidth(1.0f);

    glPushMatrix();
    glDisable(GL_LIGHTING);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // Render points
    NxU32 NbPoints = data.getNbPoints();
    if (NbPoints)
    {
    ...
    }

    // Render lines
    NxU32 NbLines = data.getNbLines();
    if (NbLines)
    {
    ...
    }

    // Render triangles
    NxU32 NbTris = data.getNbTriangles();
    if (NbTris)
    {
    ...
    }

    // Reset the color
    glColor3f(1,1,1);

    glEnable(GL_LIGHTING);
    glPopMatrix();
}

Заметьте, что рисование отладочных каркасов (debug wireframes) происходит только в отладочном режиме. Это рисование не оптимально, т.к. это не делается параллельно симуляции, т.е. физическая симуляция простаивает, когда происходит рендериг отладочной информации.

Более подробная информация представлена в "Lesson 301: Debug Renderer".

Страницы: 1 2 3