netlib.narod.ru | < Назад | Оглавление | Далее > |
Анимацией вершин называется изменение местоположения одной или нескольких вершин сетки. Реализовать этот вид анимации достаточно просто, гораздо труднее решить когда и куда перемещать вершины.
Анимация вершин может быть выполнена как с использованием интерфейса Direct3DRMMeshBuilder, так и с помощью интерфейса Direct3DRMMesh. Поскольку применение интерфейса Direct3DRMMeshBuilder приводит к снижению быстродействия и увеличению объема используемой памяти, в данной главе мы будем использовать исключительно интерфейс Direct3DRMMesh.
В приложении Cube анимация вершин применяется для изменения формы куба. Поскольку при написании программы во главу угла ставилась простота примера, а не красота результата, анимируются только две вершины. Вид окна приложения Cube показан на рис. 8.1.
Рис. 8.1. Приложение Cube
Кроме анимации вершин в приложении выполняется вращение куба путем назначения случайных векторов вращения. Это позволяет скрыть излишнюю простоту анимации вершин. Приложение Cube также предоставляет пользователю стандартное меню Render, позволяющее изменять метод визуализации сетки во время работы программы.
Приложение Cube демонстрирует нам использование следующих технологий:
Основная функциональность приложения Cube обеспечивается классом CubeWin:
class CubeWin : public RMWin { public: CubeWin(); BOOL CreateScene(); protected: //{{AFX_MSG(CubeWin) afx_msg void OnRenderWireframe(); afx_msg void OnRenderFlat(); afx_msg void OnRenderGouraud(); afx_msg void OnUpdateRenderWireframe(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderFlat(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGouraud(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: static void UpdateCube(LPDIRECT3DRMFRAME, void*, D3DVALUE); private: LPDIRECT3DRMMESH mesh; D3DRMGROUPINDEX group; };
В классе объявлены две открытые функции: конструктор и функция CreateScene(). Конструктор используется для инициализации членов данных класса. Функция CreateScene() создает сцену приложения, и ее мы рассмотрим чуть позже.
Шесть защищенных функций необходимы для реализации меню Render.
Далее следует объявление функции обратного вызова UpdateCube(), которая будет выполнять анимацию вершин.
Кроме того, вы можете видеть объявления двух переменных класса. Первая является указателем на интерфейс Direct3DRMMesh, который после инициализации будет указывать на единственную сетку приложения. Вторая переменная — это идентификатор группы граней сетки. Ее мы будем использовать при различных манипуляциях с созданной сеткой куба.
Сцена приложения Cube создается в функции CreateScene(), код которой представлен в листинге 8.1.
Листинг 8.1. Функция CubeWin::CreateScene() |
BOOL CubeWin::CreateScene() { // ------- СЕТКА -------- d3drm->CreateMesh(&mesh); mesh->AddGroup(24, 6, 4, vertorder, &group); mesh->SetVertices(group, 0, 24, vertexlist); mesh->Translate(D3DVALUE(-0.5), D3DVALUE(-0.5), D3DVALUE(-0.5)); mesh->Scale(D3DVALUE(12), D3DVALUE(12), D3DVALUE(12)); //-------- ТЕКСТУРА ------ HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_WIN95TEXTURE), "TEXTURE"); LPDIRECT3DRMTEXTURE texture; d3drm->LoadTextureFromResource(texture_id, &texture); mesh->SetGroupTexture(group, texture); mesh->SetGroupMapping(group, D3DRMMAP_PERSPCORRECT); texture->Release(); texture = 0; //------- ФРЕЙМ -------- LPDIRECT3DRMFRAME frame; d3drm->CreateFrame(scene, &frame); frame->AddVisual(mesh); frame->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.04)); static CallbackData cbdata; cbdata.mesh = mesh; cbdata.group = group; frame->AddMoveCallback(UpdateCube, &cbdata); frame->Release(); frame = 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->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); lightframe->AddLight(dlight); lightframe->AddLight(alight); dlight->Release(); dlight = 0; alight->Release(); alight = 0; lightframe->Release(); lightframe = 0; //------ КАМЕРА ---------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; } |
Функция CreateScene() выполняет следующие действия:
На первом этапе мы воспользуемся интерфейсом Direct3DRMMesh чтобы создать сетку куба. Давайте взглянем на код:
d3drm->CreateMesh(&mesh); mesh->AddGroup(24, 6, 4, vertorder, &group); mesh->SetVertices(group, 0, 24, vertexlist); mesh->Translate(D3DVALUE(-0.5), D3DVALUE(-0.5), D3DVALUE(-0.5)); mesh->Scale(D3DVALUE(12), D3DVALUE(12), D3DVALUE(12));
Сперва для инициализации указателя mesh вызывается функция CreateMesh() интерфейса Direct3DRM. Обратите внимание, что ранее в тех приложениях, где использовался интерфейс Direct3DRMMesh, для создания сетки применялась функция CreateMesh() интерфейса Direct3DRMMeshBuilder, а не функция CreateMesh() интерфейса Direct3DRM. Создать сетку с помощью конструктора сеток очень легко, потому что можно использовать функцию Load() интерфейса Direct3DRMMeshBuilder чтобы загрузить сетку из файла. Поскольку мы используем функцию CreateMesh() интерфейса Direct3DRM, у нас будет создана пустая сетка.
Затем для инициализации сетки вызывается функция AddGroup() интерфейса Direct3DRMMesh. В результате будет создана группа граней сетки. Группой называется набор граней одной сетки, которыми можно управлять вместе как единой сущностью. Первым аргументом функции AddGroup() является количество вершин в группе. У нашего куба 24 вершины, поскольку он состоит из 6 граней у каждой из которых по 4 вершины. Второй аргумент функции AddGroup() задает количество граней в группе, а третий — количество вершин у каждой из граней. Четвертый аргумент функции AddGroup() — это массив индексов вершин. Определение массива vertorder, используемого в нашей программе в качестве четвертого аргумента, выглядит так:
unsigned vertorder[] = { 0,1,2,3,4,5,6,7,8,9,10,11, 12,13,14,15,16,17,18,19,20,21,22,23 };
Данный массив задает порядок вершин. В нашем случае индексы отсортированы в простейшем из возможных, последовательном порядке. Когда сетка создается с нуля, как это делаем мы, применение непоследовательного порядка вершин не дает особых преимуществ. Однако в данных сеток, экспортируемых из программ трехмерного моделирования, таких как 3D Studio, вершины вряд ли будут отсортированы по порядку номеров.
Пятый и последний аргумент функции AddGroup() это указатель на член данных group. Функция AddGroup() инициализирует эту переменную, записывая в нее идентификатор, который в последующих вызовах функций будет применяться для определения интересующей нас группы граней сетки. Если сетка содержит только одну группу граней (как в нашем случае), в качестве идентификатора группы используется ноль.
Функция AddGroup() добавляет грани к сетке и устанавливает порядок вершин, но все данные граней имеют устанавливаемые по умолчанию значения. Значения всех координат и нормалей новых вершин равны нулю. Новые грани окрашены в белый цвет и на них не наложены никакие текстуры. Чтобы присвоить начальные значения вершинам, необходимо воспользоваться функцией SetVertices():
mesh->SetVertices(group, 0, 24, vertexlist);
Функция SetVertices() присваивает значения координат, нормалей и позицию текстуры для одной или нескольких вершин группы граней сетки. Первый аргумент функции SetVertices() определяет модифицируемую группу граней сетки. Мы используем член данных group, который был инициализирован при вызове функции AddGroup(). Второй аргумент — это номер вершины с которой мы начнем изменения (индекс первой модифицируемой вершины). Третий аргумент — число модифицируемых вершин. Поскольку мы хотим задать значения для всех 24 вершин в группе граней сетки, используем значения 0 и 24. Четвертый аргумент является массивом структур D3DRMVERTEX в котором содержатся новые данные для вершин.
Перед тем, как взглянуть на массив vertexlist (используемый в качестве четвертого аргумента функции SetVertices()), давайте поговорим о структуре D3DRMVERTEX. Ее определение в Direct3D выглядит следующим образом:
typedef struct _D3DRMVERTEX { D3DVECTOR position; D3DVECTOR normal; D3DVALUE tu, tv; D3DCOLOR color; } D3DRMVERTEX;
Вектор position используется чтобы задать местоположение вершины. Вектор normal задает вектор нормали для вершины. Поля tu и tv определяют координаты текстуры. Если на сетку будет наложена текстура, эти два значения определяют какая именно точка текстуры совпадет с вершиной. И, наконец, поле color задает цвет вершины.
Чтобы в нашей программе определить сетку куба, необходимо предоставить массив структур D3DRMVERTEX. Для упрощения этой задачи мы воспользуемся следующим макросом:
#define VERTEX(px,py,pz,nx,ny,nz,tu,tv) \ { { D3DVALUE(px),D3DVALUE(py),D3DVALUE(pz) }, \ { D3DVALUE(nx),D3DVALUE(ny),D3DVALUE(nz), }, \ D3DVALUE(tu),D3DVALUE(tv),D3DCOLOR(0) }
Макрос получает восемь аргументов и создает из них одну структуру D3DRMVERTEX. Главная польза этого макроса в том, что он избавляет нас от необходимости загромождать объявление массива операциями приведения каждого поля структуры D3DRMVERTEX к типу D3DVALUE. Вот как выглядит определение массива вершин vertexlist, используемого в приложении Cube для инициализации сетки:
static D3DRMVERTEX vertexlist[]= { // левая грань VERTEX( 0,0,0, -1,0,0, 0,1 ), // вершина 0 VERTEX( 0,0,1, -1,0,0, 0,0 ), VERTEX( 0,1,1, -1,0,0, 1,0 ), VERTEX( 0,1,0, -1,0,0, 1,1 ), // правая грань VERTEX( 1,0,0, 1,0,0, 0,0 ), VERTEX( 1,1,0, 1,0,0, 1,0 ), VERTEX( 1,1,1, 1,0,0, 1,1 ), // вершина 6 VERTEX( 1,0,1, 1,0,0, 0,1 ), // передняя грань VERTEX( 0,0,0, 0,0,-1, 0,0 ), // вершина 8 VERTEX( 0,1,0, 0,0,-1, 1,0 ), VERTEX( 1,1,0, 0,0,-1, 1,1 ), VERTEX( 1,0,0, 0,0,-1, 0,1 ), // задняя грань VERTEX( 0,0,1, 0,0,1, 0,1 ), VERTEX( 1,0,1, 0,0,1, 0,0 ), VERTEX( 1,1,1, 0,0,1, 1,0 ), // вершина 14 VERTEX( 0,1,1, 0,0,1, 1,1 ), // верхняя грань VERTEX( 0,1,0, 0,1,0, 0,0 ), VERTEX( 0,1,1, 0,1,0, 1,0 ), VERTEX( 1,1,1, 0,1,0, 1,1 ), // вершина 18 VERTEX( 1,1,0, 0,1,0, 0,1 ), // нижняя грань VERTEX( 0,0,0, 0,-1,0, 0,0 ), // вершина 20 VERTEX( 1,0,0, 0,-1,0, 1,0 ), VERTEX( 1,0,1, 0,-1,0, 1,1 ), VERTEX( 0,0,1, 0,-1,0, 0,1 ), };
В коде выделено шесть групп по четыре вершины. Каждая группа из четырех вершин описывает одну из граней куба. Первые три аргумента в описании каждой из вершин задают ее координаты. Следующие три аргумента определяют нормаль к вершине. Обратите внимание, что нормали всех вершин одной грани одинаковы. Это обеспечивает четкий контраст между примыкающими друг к другу гранями. Последние два аргумента являются координатами текстуры.
Обратите внимание на комментарии, расположенные справа от описания шести из вершин. Таким образом отмечены те вершины, которые будут анимироваться.
Вернемся к коду создания сетки (этапу 1) в функции CreateScene(). Последние два вызова функций выглядят так:
mesh->Translate(D3DVALUE(-0.5), D3DVALUE(-0.5), D3DVALUE(-0.5)); mesh->Scale(D3DVALUE(12), D3DVALUE(12), D3DVALUE(12));
Функция Translate() применяется для настройки осей сетки. Куб создан таким образом, что в начале координат расположена одна из его вершин. Мы используем функцию Translate() для перемещения куба таким образом, чтобы с началом координат совпадал его центр. Функция Scale() используется чтобы увеличить куб в 12 раз. Обратите внимание, что порядок вызова этих двух функций очень важен. Если мы выполним операцию масштабирования перед операцией перемещения, начало координат не будет совпадать с центром куба.
На втором этапе своей работы функция CreateScene() создает текстуру и накладывает ее на созданную сетку:
HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_WIN95TEXTURE), "TEXTURE"); LPDIRECT3DRMTEXTURE texture; d3drm->LoadTextureFromResource(texture_id, &texture); mesh->SetGroupTexture(group, texture); mesh->SetGroupMapping(group, D3DRMMAP_PERSPCORRECT); texture->Release(); texture = 0;
Текстура загружается из ресурсов программы функцией LoadTextureFromResource() интерфейса Direct3DRM. Затем вызывается функция SetGroupTexture() интерфейса Direct3DRMMesh чтобы связать текстуру с сеткой. Для разрешения перспективной коррекции используется функция SetGroupMapping() интерфейса Direct3DRMMesh. Обратите внимание, что обе эти функции требуют, чтобы в первом аргументе им был передан идентификатор группы граней сетки.
На третьем этапе создается фрейм для сетки:
LPDIRECT3DRMFRAME frame; d3drm->CreateFrame(scene, &frame); frame->AddVisual(mesh); frame->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.04)); static CallbackData cbdata; cbdata.mesh = mesh; cbdata.group = group; frame->AddMoveCallback(UpdateCube, &cbdata); frame->Release(); frame = 0;
Новый фрейм будет потомком фрейма scene и создается с помощью функции CreateFrame() интерфейса Direct3DRM. Сетка присоединяется к новому фрейму функцией AddVisual() интерфейса Direct3DRMFrame. С помощью функции SetRotation() новому фрейму назначается атрибут вращения. Этот атрибут является временным, поскольку присутствующая в приложении функция обратного вызова будет периодически изменять атрибут вращения фрейма сетки.
Далее объявляется статическая структура CallbackData. Эта структура используется в приложении Cube для передачи необходимых данных в функцию обратного вызова. Она объявлена статической потому что используется после того, как функция CreateScene() завершит свою работу, и объявленные в ней локальные переменные перестанут существовать.
В структуре сохраняются указатель на сетку и идентификатор группы граней сетки. Затем выполняется установка функции обратного вызова (UpdateCube()) с помощью функции AddMoveCallback() интерфейса Direct3DRMFrame. Указатель на структуру CallbackData передается функции AddMoveCallback() во втором аргументе.
На четвертом и пятом этапах работы функции CreateScene() выполняется создание источника света и порта просмотра. Мы не будем обсуждать эти действия здесь, поскольку они подробно рассмотрены в других главах и не имеют отношения к анимации вершин.
Функция UpdateCube() является функцией обратного вызова, которая выполняет анимацию вершин в приложении Cube. Код функции UpdateCube() выглядит так:
void CubeWin::UpdateCube(LPDIRECT3DRMFRAME frame, void* p, D3DVALUE) { CallbackData* data = (CallbackData*)p; static const D3DVALUE lim = D3DVALUE(5); static D3DVALUE control; static D3DVALUE inc = D3DVALUE(.25); static D3DRMVERTEX vert[24]; data->mesh->GetVertices(data->group, 0, 24, vert); vert[0].position.x += inc; vert[0].position.y += inc; vert[0].position.z += inc; vert[6].position.x += inc; vert[6].position.y += inc; vert[6].position.z += inc; vert[8].position.x += inc; vert[8].position.y += inc; vert[8].position.z += inc; vert[14].position.x += inc; vert[14].position.y += inc; vert[14].position.z += inc; vert[18].position.x += inc; vert[18].position.y += inc; vert[18].position.z += inc; vert[20].position.x += inc; vert[20].position.y += inc; vert[20].position.z += inc; data->mesh->SetVertices(data->group, 0, 24, vert); control += inc; if (control > lim || control < -lim) inc = -inc; static UINT delay; if (++delay < 20) return; delay = 0; LPDIRECT3DRMFRAME scene; frame->GetScene(&scene); D3DVECTOR spinvect; D3DRMVectorRandom(&spinvect); D3DVALUE spin = D3DDivide(rand() % 50 + 1, 400); frame->SetRotation(scene, spinvect.x, spinvect.y, spinvect.z, spin); }
Сначала функция подготавливает указатель на структуру CallbackData:
CallbackData* data = (CallbackData*)p;
Параметр p — это указатель на статическую структуру cbdata объявленную в функции CreateScene(). В то же время, переменная p объявлена в объявлении функции как указатель на void. По этой причине мы для доступа к необходимым данным будем использовать в функции обратного вызова локальный указатель data, присвоив ему приведенное к требуемому типу значение указателя p.
Затем следует объявление четырех статических переменных:
static const D3DVALUE lim = D3DVALUE(5); static D3DVALUE control; static D3DVALUE inc = D3DVALUE(.25); static D3DRMVERTEX vert[24];
Переменная lim является константой, ограничивающей перемещение вершин. Переменная control используется для определения текущего местоположения анимируемых вершин. Переменная inc хранит значение на которое будет изменяться значение переменной control. И, наконец, переменная vert является массивом структур D3DRMVERTEX. Мы будем использовать ее при получении, изменении и присваивании данных вершин сетки.
Теперь для получения текущих данных вершин сетки используется функция GetVertices() интерфейса Direct3DRMMesh:
data->mesh->GetVertices(data->group, 0, 24, vert);
Список аргументов функции GetVertices() аналогичен списку аргументов функции SetVertices(). Обратите внимание, что и указатель на сетку и идентификатор группы граней сетки передаются через указатель data. После вызова функции GetVertices() массив vert будет заполнен текущими параметрами вершин сетки.
Настало время изменить координаты вершин сетки:
vert[0].position.x += inc; vert[0].position.y += inc; vert[0].position.z += inc; vert[6].position.x += inc; vert[6].position.y += inc; vert[6].position.z += inc; vert[8].position.x += inc; vert[8].position.y += inc; vert[8].position.z += inc; vert[14].position.x += inc; vert[14].position.y += inc; vert[14].position.z += inc; vert[18].position.x += inc; vert[18].position.y += inc; vert[18].position.z += inc; vert[20].position.x += inc; vert[20].position.y += inc; vert[20].position.z += inc;
Каждая из координат изменяется на значение, хранящееся в переменной inc. Порядок изменения координат вершин не имеет значения, поскольку все эти изменения не вступят в силу до вызова функции SetVertices():
data->mesh->SetVertices(data->group, 0, 24, vert);
Функция SetVertices() изменяет сетку, присваивая ее вершинам измененные параметры.
Затем изменяется значение переменной control:
control += inc; if (control > lim || control < -lim) inc = -inc;
В этом коде используется переменная lim чтобы изменять значение переменной inc если значение переменной control достигло заданного предела.
Оставшаяся часть функции UpdateCube() осуществляет периодическое изменение атрибутов вращения фрейма:
static UINT delay; if (++delay < 20) return; delay = 0; LPDIRECT3DRMFRAME scene; frame->GetScene(&scene); D3DVECTOR spinvect; D3DRMVectorRandom(&spinvect); D3DVALUE spin = D3DDivide(rand() % 50 + 1, 400); frame->SetRotation(scene, spinvect.x, spinvect.y, spinvect.z, spin);
После каждых 20 обновлений экрана этот код вычисляет новые вектор и скорость вращения. Затем новые значения устанавливаются функцией SetRotation() интерфейса Direct3DRMFrame.
Оставшиеся функции класса CubeWin предназначены для реализации меню Render. Их код приведен ниже:
void CubeWin::OnRenderWireframe() { if (mesh) mesh->SetGroupQuality(group, D3DRMRENDER_WIREFRAME); } void CubeWin::OnRenderFlat() { if (mesh) mesh->SetGroupQuality(group, D3DRMRENDER_FLAT); } void CubeWin::OnRenderGouraud() { if (mesh) mesh->SetGroupQuality(group, D3DRMRENDER_GOURAUD); } void CubeWin::OnUpdateRenderWireframe(CCmdUI* pCmdUI) { if (mesh) { D3DRMRENDERQUALITY meshquality = mesh->GetGroupQuality(group); pCmdUI->SetCheck(meshquality == D3DRMRENDER_WIREFRAME); } } void CubeWin::OnUpdateRenderFlat(CCmdUI* pCmdUI) { if (mesh) { D3DRMRENDERQUALITY meshquality = mesh->GetGroupQuality(group); pCmdUI->SetCheck(meshquality == D3DRMRENDER_FLAT); } } void CubeWin::OnUpdateRenderGouraud(CCmdUI* pCmdUI) { if (mesh) { D3DRMRENDERQUALITY meshquality = mesh->GetGroupQuality(group); pCmdUI->SetCheck(meshquality == D3DRMRENDER_GOURAUD); } }
Обратите внимание, что для получения и изменения параметров визуализации сетки используется идентификатор групп граней сетки group.
Кроме того, вы можете заметить, что при выборе команд меню Render практически не заметна разница между плоским методом визуализации и визуализацией по методу Гуро. Обычно визуализация по методу Гуро смягчает изображение граней и углов. Однако, при выборе метода Гуро в приложении Cube грани сетки остаются ясно различимыми. Это происходит потому, что нормали, которые мы использовали при создании сетки вычисялись не тем способом, который использует для вычисления нормалей интерфейс Direct3DRMMeshBuilder. Используемые в приложении Cube нормали перпендикулярны той грани, частью которой является данная вершина. Чтобы вычислить нормаль с учетом всех граней, сходящихся в данной вершине, следует использовать функцию GenerateNormals() интерфейса Direct3DRMMeshBuilder.
netlib.narod.ru | < Назад | Оглавление | Далее > |