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

Еще об анимации

Как я уже упоминал в начале главы, анимация — очень обширная тема. Мы закончим эту главу изучением еще одного демонстрационного приложения. Приложение Target, в отличие от других приложений, рассматриваемых в этой книге, разрабатывалось не для того, чтобы показать какую-то отдельную технику. В нем совместно используются три метода анимации.

Приложение Target

Приложение Target создает сцену, в которой группа ракет или реактивных снарядов следит за целью. Движение цели определяется анимационной последовательностью, подобной той, которая использовалась для анимации сетки ракеты в приложении Rocket. Анимируется каждая ракета. Все они следят за движением цели. Кроме того, в приложении Target используется анимация камеры. Камера облетает сцену по орбите и всегда направлена на ракеты. Окно приложения изображено на рис. 7.5.


Рис. 7.5. Приложение Target

Рис. 7.5. Приложение Target


Приложение Target демонстрирует следующие технологии:

Мы поговорим о каждой из этих техник в ходе обсуждения кода приложения Target.

Класс TargetWin

Функциональность приложения Target реализована в классе TargetWin:

class TargetWin : public RMWin
{
public:
    TargetWin();
    BOOL CreateScene();
protected:
    //{{AFX_MSG(TargetWin)
    afx_msg void OnRenderWireframe();
    afx_msg void OnRenderFlat();
    afx_msg void OnRenderGouraud();
    afx_msg void OnUpdateRenderFlat(CCmdUI* pCmdUI);
    afx_msg void OnUpdateRenderGouraud(CCmdUI* pCmdUI);
    afx_msg void OnUpdateRenderWireframe(CCmdUI* pCmdUI);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
private:
    static void OrientFrame(LPDIRECT3DRMFRAME frame, void*, D3DVALUE);
    static void MoveTarget(LPDIRECT3DRMFRAME frame, void*, D3DVALUE);
private:
    LPDIRECT3DRMMESHBUILDER meshbuilder;
};

В классе объявлены две открытые функции: конструктор и функция CreateScene(). Конструктор инициализирует единственную переменную класса, а функция CreateScene() создает сцену для приложения.

Следом идет объявление шести защищенных функций. Все они предназначены для реализации меню Render.

Потом объявлены две защищенные функции обратного вызова: OrientFrame() и MoveTarget(). Функция OrientFrame() используется для изменения ориентации ракет таким образом, чтобы они всегда были направлены на цель. Функция MoveTarget() применяется для перемещения цели.

И в самом конце объявляется единственная переменная класса. Указатель meshbuilder используется для загрузки сетки, изображающей ракету. Эта переменная используется как в функции CreateScene() так и в функциях меню Render.

Функция TargetWin::CreateScene()

Код функции CreateScene() приложения Target приведен в листинге 7.3.

Листинг 7.3. Функция TargetWin::CreateScene()

BOOL TargetWin::CreateScene()
{
    // ------- СЕТКА ЦЕЛИ --------
    D3DRMLOADRESOURCE resinfo;
    resinfo.hModule = NULL;
    resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH);
    resinfo.lpType = "MESH";
    LPDIRECT3DRMMESHBUILDER targetbuilder;
    d3drm->CreateMeshBuilder(&targetbuilder);
    targetbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE,
            NULL, NULL);
    ScaleMesh(targetbuilder, D3DVALUE(.75));

    // --------- АНИМАЦИЯ ЦЕЛИ ----------
    LPDIRECT3DRMANIMATION animation;
    d3drm->CreateAnimation(&animation);
    animation->SetOptions(D3DRMANIMATION_SPLINEPOSITION |
            D3DRMANIMATION_CLOSED |
            D3DRMANIMATION_POSITION);
    animation->AddPositionKey(D3DVALUE(0),
            D3DVALUE(-20), D3DVALUE(0), D3DVALUE(-20));
    animation->AddPositionKey(D3DVALUE(12),
            D3DVALUE(0), D3DVALUE(15), D3DVALUE(0));
    animation->AddPositionKey(D3DVALUE(24),
            D3DVALUE(20), D3DVALUE(0), D3DVALUE(-20));
    animation->AddPositionKey(D3DVALUE(35),
            D3DVALUE(0), D3DVALUE(0), D3DVALUE(0));
    animation->AddPositionKey(D3DVALUE(49),
            D3DVALUE(20), D3DVALUE(0), D3DVALUE(20));
    animation->AddPositionKey(D3DVALUE(65),
            D3DVALUE(0), D3DVALUE(15), D3DVALUE(0));
    animation->AddPositionKey(D3DVALUE(74),
            D3DVALUE(-20), D3DVALUE(0), D3DVALUE(20));
    animation->AddPositionKey(D3DVALUE(85),
            D3DVALUE(0), D3DVALUE(0), D3DVALUE(0));
    animation->AddPositionKey(D3DVALUE(99),
            D3DVALUE(-20), D3DVALUE(0), D3DVALUE(-20));

    // ---------- ФРЕЙМ ЦЕЛИ --------
    LPDIRECT3DRMFRAME targetframe;
    d3drm->CreateFrame(scene, &targetframe);
    animation->SetFrame(targetframe);
    targetframe->AddVisual(targetbuilder);
    targetframe->AddMoveCallback(MoveTarget, animation);

    targetbuilder->Release();
    targetbuilder = 0;

    // ------- СЕТКА РАКЕТЫ --------
    resinfo.hModule = NULL;
    resinfo.lpName = MAKEINTRESOURCE(IDR_MISSLEMESH);
    resinfo.lpType = "MESH";
    d3drm->CreateMeshBuilder(&meshbuilder);
    meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE,
            NULL, NULL);
    meshbuilder->SetColorRGB(
            D3DVALUE(.67), D3DVALUE(.82), D3DVALUE(.94));
    meshbuilder->SetQuality(D3DRMRENDER_FLAT);
    ScaleMesh(meshbuilder, D3DVALUE(7));

    // ------- ФРЕЙМЫ РАКЕТ ------
    for (int i = 0; i < 5; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            LPDIRECT3DRMFRAME meshframe;
            d3drm->CreateFrame(scene, &meshframe);

            meshframe->SetPosition(scene,
                    D3DVALUE((i - 2) * 8),
                    D3DVALUE(-12),
                    D3DVALUE((j - 1) * 8));
            meshframe->AddVisual(meshbuilder);
            meshframe->AddMoveCallback(OrientFrame, targetframe);

            meshframe->Release();
            meshframe = 0;
        }
    }

    // --------НАПРАВЛЕННЫЙ СВЕТ--------
    LPDIRECT3DRMLIGHT dlight;
    d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL,
            D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00),
            &dlight);
    LPDIRECT3DRMLIGHT alight;
    d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT,
            D3DVALUE(0.50), D3DVALUE(0.50), D3DVALUE(0.50),
            &alight);

    LPDIRECT3DRMFRAME lightframe;
    d3drm->CreateFrame(scene, &lightframe);
    lightframe->AddLight(dlight);
    lightframe->AddLight(alight);
    lightframe->SetOrientation(scene,
            D3DVALUE(0), D3DVALUE(-1), D3DVALUE(0),
            D3DVALUE(0), D3DVALUE(0), D3DVALUE(1));
    alight->Release();
    alight = 0;
    dlight->Release();
    dlight = 0;
    lightframe->Release();
    lightframe = 0;

    //------ КАМЕРА----------
    LPDIRECT3DRMFRAME cameradummy;
    d3drm->CreateFrame(scene, &cameradummy);
    cameradummy->SetRotation(scene,
            D3DVALUE(0), D3DVALUE(1), D3DVALUE(0),
            D3DVALUE(.01));
    d3drm->CreateFrame(cameradummy, &camera);
    camera->SetPosition(scene,
            D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50));
    d3drm->CreateViewport(device, camera, 0, 0,
            device->GetWidth(), device->GetHeight(),
            &viewport);

    return TRUE;
}

Функция CreateScene() выполняет следующие действия:

  1. Загружает сферическую сетку, которая будет представлять цель для ракет.
  2. Создает анимационную последовательность для сетки цели.
  3. Создает фрейм для сетки цели и устанавливает функцию обратного вызова для обновления анимационной последовательности.
  4. Загружает сетку ракеты.
  5. Создает 15 фреймов и к каждому из них присоединяет сетку ракеты.
  6. Создает два источника света.
  7. Создает порт просмотра.

Первый этап — это создание сетки для цели:

D3DRMLOADRESOURCE resinfo;
resinfo.hModule = NULL;
resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH);
resinfo.lpType = "MESH";
LPDIRECT3DRMMESHBUILDER targetbuilder;
d3drm->CreateMeshBuilder(&targetbuilder);
targetbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE,
        NULL, NULL);
ScaleMesh(targetbuilder, D3DVALUE(.75));

Сетка загружается из ресурсов программы. Структура resinfo идентифицирует элемент ресурсов, содержащий сетку. Для загрузки сетки, как обычно, используется функция Load() интерфейса Direct3DRMMeshBuilder. После загрузки сетка масштабируется функцией RMWin::ScaleMesh().

На втором этапе создается анимационная последовательность, использующаяся для анимации сетки цели:

LPDIRECT3DRMANIMATION animation;
d3drm->CreateAnimation(&animation);
animation->SetOptions(D3DRMANIMATION_SPLINEPOSITION |
        D3DRMANIMATION_CLOSED |
        D3DRMANIMATION_POSITION);
animation->AddPositionKey(D3DVALUE(0),
        D3DVALUE(-20), D3DVALUE(0), D3DVALUE(-20));
animation->AddPositionKey(D3DVALUE(12),
        D3DVALUE(0), D3DVALUE(15), D3DVALUE(0));
animation->AddPositionKey(D3DVALUE(24),
        D3DVALUE(20), D3DVALUE(0), D3DVALUE(-20));
animation->AddPositionKey(D3DVALUE(35),
        D3DVALUE(0), D3DVALUE(0), D3DVALUE(0));
animation->AddPositionKey(D3DVALUE(49),
        D3DVALUE(20), D3DVALUE(0), D3DVALUE(20));
animation->AddPositionKey(D3DVALUE(65),
        D3DVALUE(0), D3DVALUE(15), D3DVALUE(0));
animation->AddPositionKey(D3DVALUE(74),
        D3DVALUE(-20), D3DVALUE(0), D3DVALUE(20));
animation->AddPositionKey(D3DVALUE(85),
        D3DVALUE(0), D3DVALUE(0), D3DVALUE(0));
animation->AddPositionKey(D3DVALUE(99),
        D3DVALUE(-20), D3DVALUE(0), D3DVALUE(-20));

Сначала для инициализации экземпляра интерфейса Direct3DRMAnimation вызывается функция CreateAnimation() интерфейса Direct3DRM. Затем вызывается функция SetOptions() интерфейса Direct3DRMAnimation,которой передается три флага. Флаг D3DRMANIMATION_SPLINEPOSITION указывает, что при расчете анимационной последовательности будут применяться сплайны. Флаг D3DRMANIMATION_CLOSED позволяет использовать непрерывно увеличивающиеся значения временных меток для повторного выполнения анимационной последовательности. Флаг D3DRMANIMATION_POSITION указывает объекту анимации, что нас интересует изменение местоположения анимируемого фрейма. Обратите внимание, что флаг D3DRMANIMATION_SCALEANDROTATION отсутствует (мы использовали его в приложении Rocket). Благодаря этому объект анимации не выполняет вычисления для изменения ориентации и масштаба, что позволяет анимационной последовательности выполняться быстрее.

Оставшаяся часть кода, относящегося ко второму этапу работы функции представляет собой несколько вызовов функции AddPositionKey() интерфейса Direct3DRMAnimation. Когда мы создавали анимационную последовательность в приложении Rocket, то использовали цикл для добавления ключей, которые были определены в элементах массива. В приложении Target не используется ни цикл ни массив. Каждый ключ добавляется с помощью отдельного вызова функции AddPositionKey(). Также, как и в приложении Rocket, используемые для анимационной последовательности позиции выбраны экспериментальным путем.

На следующем этапе выполняется создание фрейма для размещения и анимации сетки цели:

LPDIRECT3DRMFRAME targetframe;
d3drm->CreateFrame(scene, &targetframe);
animation->SetFrame(targetframe);
targetframe->AddVisual(targetbuilder);
targetframe->AddMoveCallback(MoveTarget, animation);

targetbuilder->Release();
targetbuilder = 0;

Сначала для инициализации локального указателя targetframe используется функция CreateFrame() интерфейса Direct3DRM. Затем указатель на новый фрейм используется в качестве аргумента функции SetFrame() интерфейса Direct3DRMAnimation. Этот вызов функции связывает фрейм с анимационной последовательностью, после чего местоположение фрейма контролируется объектом анимации.

Функция AddVisual() интерфейса Direct3DRMFrame используется чтобы присоединить к фрейму сетку цели. Затем с помощью функции AddMoveCallback() интерфейса Direct3DRMFrame устанавливается функция обратного вызова MoveTarget(). Обратите внимание, что в качестве второго аргумента функции AddMoveCallback() передается указатель animation. Это обеспечивает возможность доступа к объекту анимации для функции обратного вызова. В заключение, освобождается указатель targetbuilder (который был инициализирован на первом этапе).


СОВЕТ


Нарушение соглашений COM. Обычно, перед завершением функции освобождаются все локальные указатели на интерфейсы Direct3D. В приложении Target мы видим два исключения из этого правила. Указатели animation и targetframe не освобождаются, поскольку используются в функциях обратного вызова. Освобождение этих указателей приведет к тому, что COM уничтожит соответствующие объекты, и обращение к функции обратного вызова приведет к краху программы.

Другое возможное решение — оставить вызов функции Release(), но только после вызова функции AddRef(). Благодаря этому COM получает уведомление о создании дополнительной ссылки на объект. Согласно спецификации COM второй метод предпочтительнее. Мы используем первый метод только для того, чтобы сделать код приложения как можно более простым.


На четвертом этапе загружается сетка, изображающая ракету:

resinfo.hModule = NULL;
resinfo.lpName = MAKEINTRESOURCE(IDR_MISSLEMESH);
resinfo.lpType = "MESH";
d3drm->CreateMeshBuilder(&meshbuilder);
meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE,
        NULL, NULL);
meshbuilder->SetColorRGB(
        D3DVALUE(.67), D3DVALUE(.82), D3DVALUE(.94));
meshbuilder->SetQuality(D3DRMRENDER_FLAT);
ScaleMesh(meshbuilder, D3DVALUE(7));

Также как и сетка мишени, сетка ракеты хранится в ресурсах программы. После того, как сетка загружена, она окрашивается в светло-синий цвет, с помощью функции SetColorRGB() интерфейса Direct3DRMMeshBuilder. Функция SetQuality() вызывается для того, чтобы указать Direct3D, что при визуализации сетки ракеты должен использоваться плоский метод. Потом метод визуализации можно будет изменить с помощью меню Render. И, наконец, функция ScaleMesh() используется для масштабирования размеров сетки до семи единиц.

На пятом этапе чтобы создать 15 фреймов и присоединить к каждому из них сетку ракеты, используются вложенные циклы. Также на этом этапе для каждого фрейма устанавливается функция обратного вызова:

for (int i = 0; i < 5; i++)
{
    for (int j = 0;j < 3; j++)
    {
        LPDIRECT3DRMFRAME meshframe;
        d3drm->CreateFrame(scene, &meshframe);
        meshframe->SetPosition(scene,
                D3DVALUE((i - 2) * 8),
                D3DVALUE(-12),
                D3DVALUE((j - 1) * 8));
        meshframe->AddVisual(meshbuilder);
        meshframe->AddMoveCallback(OrientFrame, targetframe);
        meshframe->Release();
        meshframe = 0;
    }
}

Указатель meshframe используется при инициализации каждого фрейма. Позиция нового фрейма зависит от текущей итерации двух вложенных циклов. Затем функция AddVisual() интерфейса Direct3DRMFrame используется для присоединения к фрейму сетки ракеты. Функция AddMoveCallback() применяется для установки функции обратного вызова OrientFrame(). Обратите внимание, что в качестве второго аргумента функции AddMoveCallback() используется указатель targetframe. Это обеспечивает функции обратного вызова доступ к фрейму за которым будут следить ракеты. После вызова функции AddMoveCallback() указатель meshframe освобождается.

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

Функция TargetWin::MoveTarget()

Функция обратного вызова MoveTarget() обновляет анимационную последовательность один раз при каждом системном обновлении. Код функции выглядит так:

void TargetWin::MoveTarget(LPDIRECT3DRMFRAME, void* p, D3DVALUE)
{
    LPDIRECT3DRMANIMATION animation = (LPDIRECT3DRMANIMATION)p;
    static D3DVALUE time;
    time += D3DVALUE(.5);
    animation->SetTime(time);
}

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

Функция TargetWin::OrientFrame()

Функция обратного вызова OrientFrame() используется для изменения ориентации фреймов ракет в соответствии с движением цели. Эта работа совершается функцией LookAt() интерфейса Direct3DRMFrame, которая ориентирует один фрейм так, чтобы он «смотрел» на другой. Определение функции OrientFrame() выглядит следующим образом:

void TargetWin::OrientFrame(LPDIRECT3DRMFRAME frame, void* p, D3DVALUE)
{
    LPDIRECT3DRMFRAME targetframe = (LPDIRECT3DRMFRAME)p;
    LPDIRECT3DRMFRAME scene;
    frame->GetScene(&scene);
    frame->LookAt(targetframe, scene, (D3DRMFRAMECONSTRAINT)0);
}

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

И, наконец, вызывается функция LookAt(). Первый аргумент функции LookAt() — это указатель на фрейм, за которым мы хотим следить. Второй аргумент определяет систему координат. Третий аргумент позволяет ограничить перемещение фрейма вдоль осей системы координат. Мы хотим, чтобы наши фреймы ракет наслаждались полной свободой, и поэтому указываем в этом параметре 0, а не какой-либо набор ограничивающих флагов.


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

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