netlib.narod.ru< Назад | Оглавление | Далее >

Управление сценой

До сих пор мы обсуждали инициализацию Direct3D и создание сцены в нашей программе. Теперь мы обратим внимание на то, что происходит во время выполнения программы.

Функция OnIdle()

Интерфейс Direct3DRM предоставляет функцию Tick() применяемую, чтобы сообщить Direct3D о необходимости обновить все объекты сцены, выполнить визуализацию и вывести на экран полученный результат. Вызов функции Tick() осуществляет приложение, использующее Direct3D.

Наша программа вызывает функцию Tick() из функции OnIdle(). Функция OnIdle() вызывается MFC всякий раз, когда нет никаких сообщений, требующих обработки. Вызов функции Tick() из функции OnIdle() приводит к тому, что обновление будет выполняться настолько часто, насколько это возможно. Код функции OnIdle() приведен ниже:

BOOL RMApp::OnIdle(LONG)
{
    ASSERT(RMWin::d3drm);
    RMWin::d3drm->Tick(D3DVALUE(1));
    return TRUE;
}

В начале функции проверяется была ли инициализирована переменная RMWin::d3drm. Если да, то вызывается функция Tick(). Затем функция возвращает TRUE чтобы уведомить MFC о необходимости вызова функции OnIdle() в дальнейшем (если будет возвращено FALSE то MFC больше не будет вызывать функцию OnIdle()).

Существует два способа контроля скорости работы приложения с помощью функции Tick(). Первый способ уже упоминался ранее — чем чаще вызывается функция Tick(), тем чаще Direct3D выполняет обновление. Конечно, имеется предельное значение частоты вызова функции Tick(). Если ваша программа выполняется на медленной машине, время выполнения функции Tick() увеличивается.

Второй способ контролировать скорость работы программы — использование аргумента функции Tick(). Передача аргумента, равного 1.0, означает, что система выполнит полное обновление и отобразит результат. Передача меньших значений приводит к тому, что Direct3D выполняет промежуточное обновление анимации в соответствии с переданным в качестве аргумента значением.

Например, если создать сцену, содержащую объект поворачивающийся за каждый шаг анимации на четверть оборота, и вызвать функцию Tick() с аргументом 0.5, потребуется два обновления системы, чтобы объект повернулся на четверть оборота. Аналогичным образом, если аргумент функции Tick() будет равен 2, анимация будет выполняться с удвоенной скоростью.

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

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

Функция OnActivate()

Direct3D — неплохая подсистема, делающая то, о чем вы ее просите. Однако, она требует выполнения некоторых условий. Одно из них заключается в следующем — вы должны уведомлять ее всякий раз, когда ваше приложение получает сообщение WM_ACTIVATE. Для этой цели Direct3D предоставляет функцию HandleActivate(). Единственная проблема состоит в том, что функция HandleActivate() является частью интерфейса Direct3DRMWinDevice, которого пока нет в нашей программе.

Интерфейс Direct3DRMWinDevice поддерживает те же самые объекты, что и интерфейс Direct3DRMDevice, поэтому для решения нашей проблемы мы можем использовать существующий интерфейс Direct3DRMDevice для получения интерфейса Direct3DRMWinDevice.

В функции RMWin::OnActivate() запрашивается интерфейс WinDevice и вызывается его функция HandleActivate():

void RMWin::OnActivate(UINT state, CWnd* other, BOOL minimize)
{
    LPDIRECT3DRMWINDEVICE windev;
    if (device)
    {
        if (device->QueryInterface(IID_IDirect3DRMWinDevice,
          (void**)&windev) == 0)
        {
            if (windev->HandleActivate((unsigned short)
              MAKELONG((WORD)state,(WORD)0)) != 0)
                AfxMessageBox("windev->HandleActivate() failure");
            windev->Release();
        }
        else
            AfxMessageBox("device->QueryInterface(WinDevice) failure");
    }
    CFrameWnd::OnActivate(state, other, minimize);
}

Мы используем GUID IID_IDirect3DRMWinDevice, чтобы указать функции QueryInterface(), что мы ищем указатель на интерфейс Direct3DRMWinDevice. После того, как указатель на интерфейс получен, можно вызывать функцию HandleActivate(). В случае успешного завершения функции QueryInterface() и HandleActivate() возвращают ноль.

Функция OnPaint()

Direct3D также ожидает уведомления когда ваше приложение получает сообщение WM_PAINT. Для этой цели интерфейс Direct3DRMWinDevice предоставляет функцию HandlePaint(), благодаря чему функция OnPaint() очень похожа на функцию OnActivate(). Главное отличие состоит в том, что при первом вызове функции OnPaint() из нее вызывается функция CreateDevice(). Код функции OnPaint() приведен ниже:

void RMWin::OnPaint()
{
    static BOOL first = TRUE;
    if (first)
    {
        first = FALSE;
        BOOL ok = CreateDevice();
        if (!ok)
            PostQuitMessage(0);
    }

    if (GetUpdateRect(NULL, FALSE) == FALSE)
        return;

    if (device)
    {
        LPDIRECT3DRMWINDEVICE windev;
        PAINTSTRUCT ps;
        BeginPaint(&ps);
        if (device->QueryInterface(IID_IDirect3DRMWinDevice,
          (void**)&windev) == 0)
        {
            if (windev->HandlePaint(ps.hdc) != 0)
                AfxMessageBox("windev->HandlePaint() failure");
            windev->Release();
        }
        else
            AfxMessageBox("Failed to create Windows device to handle
              WM_PAINT");
        EndPaint(&ps);
    }
}

Статическая переменная flag используется, чтобы определить вызывается ли функция OnPaint() в первый раз. Если функция CreateDevice() возвращает FALSE, программа прекращает работу. Переменная flag устанавливается в TRUE, чтобы функция CreateDevice() вызывалась только один раз.

Функция GetUpdateRect() применяется, чтобы определить необходимость перерисовки. Если функция GetUpdateRect() возвращает FALSE, ни одна часть окна не требует перерисовки и выполнение функции OnPaint() завершается.

Остальная часть кода аналогична функции OnActivate(). Функция QueryInterface() используется для получения указателя на интерфейс Direct3DRMWinDevice, после чего полученный указатель используется для вызова функции HandlePaint().

Функция OnSize()

Функция OnSize() вызывается MFC при получении сообщения WM_SIZE. Это сообщение уведомляет о том, что пользователь изменил размеры окна. Класс RMWin предоставляет функцию OnSize() для изменения параметров устройства и порта просмотра в соответствии с новыми размерами окна.

Размеры устройства Direct3D не могут быть изменены. Это означает, что при изменении размеров окна существующее устройство должно быть уничтожено и заменено на новое. Если требуется уничтожить существующее устройство, функция OnSize() сохраняет текущие параметры устройства и использует их для конфигурации нового устройства. Код функции OnSize() выглядит следующим образом:

void RMWin::OnSize(UINT type, int cx, int cy)
{
    CFrameWnd::OnSize(type, cx, cy);
    if (!device)
        return;

    int width = cx;
    int height = cy;
    if (width && height)
    {
        int view_width = viewport->GetWidth();
        int view_height = viewport->GetHeight();
        int dev_width = device->GetWidth();
        int dev_height = device->GetHeight();

        if (view_width == width && view_height == height)
            return;
        int old_dither = device->GetDither();
        D3DRMRENDERQUALITY old_quality = device->GetQuality();
        int old_shades = device->GetShades();

        viewport->Release();
        device->Release();
        d3drm->CreateDeviceFromClipper(clipper, GetGUID(), width,
          height, &device);

        device->SetDither(old_dither);
        device->SetQuality(old_quality);
        device->SetShades(old_shades);

        width = device->GetWidth();
        height = device->GetHeight();
        d3drm->CreateViewport(device, camera, 0, 0, width, height,
          &viewport);
    }
}

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

Если необходимо создать новое устройство, используется функция CreateDeviceFromClipper() так же, как это делалось в функции CreateDevice(). После создания и настройки параметров нового устройства, создается новый порт просмотра.

Функция OnEraseBkgnd()

Direct3D позволяет задать цвет фона с помощью функции SetSceneBackground() интерфейса Direct3DRMFrame, но при изменении размеров окна Windows стирает содержимое клиентской области не учитывая цвет фона, установленный Direct3D. По умолчанию Windows закрашивает клиентскую область белым цветом.

MFC предоставляет функцию CWnd::OnEraseBkgnd(), позволяющую переопределить заданное по умолчанию поведение Windows. Класс RMWin предоставляет версию функции OnEraseBkgnd(), использующую для закраски клиентской области текущий цвет фона Direct3D. Код функции выглядит так:

BOOL RMWin::OnEraseBkgnd(CDC* pDC)
{
    COLORREF bgcolor;
    if (scene)
    {
        D3DCOLOR scenecolor = scene->GetSceneBackground();
        bgcolor = D3DCOLOR_2_COLORREF(scenecolor);
    }
    else
        bgcolor = RGB(0,0,0);

    CBrush br(bgcolor);
    CRect rc;
    GetClientRect(&rc);
    pDC->FillRect(&rc, &br);
    return TRUE;
}

Функция объявляет экземпляр типа COLORREF и присваивает ему текущий цвет фона Direct3D. Функция D3DCOLOR_2_COLORREF() преобразует значение типа D3DCOLOR в значение типа COLORREF. Позднее мы обсудим эту функцию. Если фрейм сцены еще не был создан, используется черный цвет.

Затем экземпляр типа COLORREF используется для создания объекта CBrush, и определяются размеры клиентской области окна. Функция CDC::FillRect() применяется для задания цвета клиентской области окна. Завершая работу, функция возвращает TRUE, чтобы уведомить MFC об успешном завершении работы.

Использование функций обратного вызова

В начале этой главы, создавая с помощью мастера Direct3D AppWizard приложение Sample, мы указали, что хотим использовать анимированный зональный источник света. Мастер AppWizard добавил в проект код, изменяющий ориентацию источника света во время выполнения программы.

Это изменение выполняет функция обратного вызова. Функции обратного вызова — это функции, которые Direct3D вызывает каждый раз, когда собирается выполнить системное обновление. Такие функции могут применяться для изменения параметров во время выполнения программы.

Когда в функции CreateScene() мы создавали зональный источник света, мы установили для него функцию обратного вызова с именем MoveLight(). Установка функции обратного вызова выглядит следующим образом:

slightframe->AddMoveCallback(MoveLight, NULL);

Функция AddMoveCallback() — это функция интерфейса Direct3DRMFrame. Первый ее аргумент — это указатель на функцию, которая будет вызываться при каждом обновлении. Второй параметр представляет собой указатель на данные, которые будут передаваться функции обратного вызова. Эти дополнительные данные являются необязательными, поэтому мы передаем NULL.

Функция MoveLight() изменяет ориентацию зонального источника света и выглядит следующим образом:

void SampleWin::MoveLight(LPDIRECT3DRMFRAME lightframe, void*, D3DVALUE)
{
    // перемещение прожектора над сеткой
    static const D3DVALUE lim = D3DVALUE(0.3);
    static D3DVALUE xi = D3DVALUE(0.01);
    static D3DVALUE yi = D3DVALUE(0.005);
    static D3DVALUE x, y;
    if (x < -LIM || x > lim)
        xi = -xi;
    if (y < -LIM || y > lim)
        yi = -yi;
    x += xi;
    y += yi;
    lightframe->SetOrientation(NULL,
        x, y-1, D3DVALUE(1),
        D3DVALUE(0), D3DVALUE(1), D3DVALUE(0));
}

Для вычисления новой ориентации источника света в функции используется простой алгоритм «подпрыгивающего мяча». Перемещение источника света ограничивается константой lim и изменяется на значения xi и yi. После вычисления новой ориентации, она назначается фрейму с помощью функции SetOrientation().

Функции обратного вызова всегда объявляются статическими, как показано ниже:

class SampleWin : public RMWin
{
// ...
private:
    static void MoveLight(LPDIRECT3DRMFRAME frame, void* arg,
      D3DVALUE delta);
// ...
};

Это требование, объясняется тем, что обычные функции-члены класса требуют неявного указателя на класс. Объявление функции статической снимает это требование, но означает, что функции обратного вызова не могут обращаться к функциям-членам класса. По этой причине функция AddMoveCallback() предоставляет способ передачи дополнительных данных в функцию обратного вызова.

Функции обратного вызова, устанавливаемые с помощью AddMoveCallback() получают три параметра. Первый — это указатель на интерфейс фрейма для которого назначена данная функция обратного вызова. Второй параметр — это указатель на необязательные дополнительные данные. Третий параметр — это значение, полученное функцией Tick(). Помните, что функция Tick() может применяться для замедления и ускорения анимации в программе. Если в качестве параметра функции Tick() вы всегда используете 1.0, то можете спокойно игнорировать третий параметр.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz