netlib.narod.ru | < Назад | Оглавление | Далее > |
Все сцены Direct3D создаются с помощью иерархий фреймов. Приложения, которые мы рассмотрели до сих пор, использовали простую иерархию, состоящую из единственного корневого фрейма и нескольких дочерних фреймов. Хотя мы везде использовали иерархии фреймов, это не бросалось в глаза, поскольку иерархии были очень простыми. На рис. 7.1 показана простая иерархия фреймов, типичная для рассмотренных ранее демонстрационных программ.
Рис. 7.1. Простая иерархия фреймов
Фреймы на рис. 7.1 названы в соответствии с их назначением. Показанная иерархия может применяться в любом приложении, где используются две сетки, источник света и камера.
В предыдущих главах мы видели несколько приложений с чуть более сложной иерархией фреймов. В приложениях Decal и Firefly применялись пустые фреймы, что усложняет иерархию и делает ее похожей на ту, которая изображена на рис. 7.2.
Рис. 7.2. Чуть более сложная иерархия фреймов
В иерархии, представленной на рис. 7.2 фрейм второй сетки (mesh2frame) присоединен к пустому фрейму, а не к корневому фрейму сцены. Это позволяет анимировать вторую сетку либо перемещая фрейм mesh2frame, либо перемещая пустой фрейм. Если вам непонятно назначение пустого фрейма, посмотрите описание демонстрационных программ Decal и Firefly.
Приложение Molecule использует иерархию фреймов для моделирования структуры гипотетической молекулы. Оно конструирует иерархию фреймов и присоединяет к каждому фрейму в иерархии сферическую сетку. Размер и цвет присоединяемой сетки зависит от местоположения фрейма в иерархии. Приложение Molecule предоставляет команды меню, позволяющие настраивать сложность структуры молекулы. Внешний вид окна приложения Molecule показан на рис. 7.3.
Рис. 7.3. Приложение Molecule
Меню Depth позволяет настраивать глубину иерархии фреймов. Значение глубины можно изменять в диапазоне от одного до шести. Если значение равно единице, молекула будет состоять всего лишь из одной сферы. Если значение равно шести, то в иерархии будет шесть уровней дочерних фреймов.
Меню Children позволяет задать количество дочерних фреймов. По умолчанию у каждого фрейма в иерархии есть два потомка. Команды меню Children позволяют изменять число потомков каждого фрейма от одного до четырех.
Эти два меню позволяют изменять иерархию фреймов приложения Molecule в широких пределах. Например, если установить значение глубины равным единице, иерархия будет состоять всего из одного фрейма. Значения по умолчанию (глубина = 4, количество потомков = 2) приведут к созданию 15 фреймов, а если задать максимально возможные значения (глубина = 6, количество потомков = 4), то будет создано 1365 фреймов.
Приложение Molecule демонстрирует следующие технологии:
Основная функциональность приложения Molecule сосредоточена в классе MoleculeWin:
class MoleculeWin : public RMWin { public: MoleculeWin(); BOOL CreateScene(); protected: //{{AFX_MSG(MoleculeWin) afx_msg void OnDepth1(); afx_msg void OnDepth2(); afx_msg void OnDepth3(); afx_msg void OnDepth4(); afx_msg void OnDepth5(); afx_msg void OnDepth6(); afx_msg void OnChildren1(); afx_msg void OnChildren2(); afx_msg void OnChildren3(); afx_msg void OnChildren4(); afx_msg void OnUpdateChildren1(CCmdUI* pCmdUI); afx_msg void OnUpdateChildren2(CCmdUI* pCmdUI); afx_msg void OnUpdateChildren3(CCmdUI* pCmdUI); afx_msg void OnUpdateChildren4(CCmdUI* pCmdUI); afx_msg void OnUpdateDepth1(CCmdUI* pCmdUI); afx_msg void OnUpdateDepth2(CCmdUI* pCmdUI); afx_msg void OnUpdateDepth3(CCmdUI* pCmdUI); afx_msg void OnUpdateDepth4(CCmdUI* pCmdUI); afx_msg void OnUpdateDepth5(CCmdUI* pCmdUI); afx_msg void OnUpdateDepth6(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: BOOL CreateHierarchy(); BOOL CreateChildren(LPDIRECT3DRMFRAME frame, int depth); private: LPDIRECT3DRMMESH mesh[MAXDEPTH]; int curdepth; int numchildren; int framecount; };
В классе объявлены две открытые функции: конструктор и функция CreateScene(). Конструктор применяется для инициализации членов данных класса. Функция CreateScene() создает начальную сцену приложения. Мы познакомимся с ней чуть позже.
Двадцать защищенных функций, являющихся обработчиками событий, установлены с помощью мастера ClassWizard. Эти функции обеспечивают работу меню Depth и Children в приложении.
Кроме того, объявлены две закрытые функции: CreateHierarchy() и CreateChildren(). Функция CreateHierarchy() отвечает за создание иерархии фреймов, соответствующей текущим параметрам приложения. Она вызывается при запуске программы и каждый раз, когда пользователь изменяет параметры иерархии фреймов.
При создании иерархии функция CreateHierarchy() использует функцию CreateChildren(). Функция CreateChildren() является рекурсивной функцией, добавляющей дочерние фреймы к существующему фрейму.
И, наконец, в классе объявлены три переменных:
LPDIRECT3DRMMESH mesh[MAXDEPTH]; int curdepth; int numchildren;
Переменная mesh представляет собой массив указателей на интерфейс Direct3DRMMesh. Мы будем использовать этот массив, чтобы хранить сетки для фреймов каждого из уровней глубины иерархии. Приложение создает шесть сеток — по одной для каждой глубины. Когда сетка присоединяется к нескольким фреймам, на экране отображается несколько ее экземпляров.
Переменные curdepth и numchildren хранят текущие параметры иерархии. Они модифицируются обработчиками событий команд меню Depth и Children, и используются в функциях CreateHierarchy() и CreateChildren(). Инициализация этих переменных выполняется конструктором класса MoleculeWin, код которого выглядит так:
MoleculeWin::MoleculeWin() { curdepth=4; numchildren=2; }
Начальная сцена приложения Molecule создается в функции CreateScene(). Код этой функции представлен в листинге 7.1.
Листинг 7.1. Функция MoleculeWin::CreateScene() |
BOOL MoleculeWin::CreateScene() { // ------- SRAND -------- srand((unsigned)time(NULL)); // ------- СЕТКИ -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER meshbuilder; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); for (int i = 0; i < MAXDEPTH; i++) { ScaleMesh(meshbuilder, D3DVALUE(MAXDEPTH - i)); D3DCOLOR clr = meshcolor[i]; D3DVALUE r = D3DRMColorGetRed(clr); D3DVALUE g = D3DRMColorGetGreen(clr); D3DVALUE b = D3DRMColorGetBlue(clr); meshbuilder->SetColorRGB(r, g, b); LPDIRECT3DRMMESH m; meshbuilder->CreateMesh(&m); mesh[i] = m; } meshbuilder->Release(); meshbuilder = 0; // -------- ИЕРАРХИЯ ФРЕЙМОВ ------ CreateHierarchy(); // --------НАПРАВЛЕННЫЙ СВЕТ-------- LPDIRECT3DRMLIGHT dlight; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &dlight); LPDIRECT3DRMFRAME dlightframe; d3drm->CreateFrame(scene, &dlightframe); dlightframe->AddLight(dlight); dlightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); dlight->Release(); dlight = 0; dlightframe->Release(); dlightframe = 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() выполняет следующие действия:
В начале вызывается функция srand() и ей в качестве аргумента передается значение, возвращаемое функцией time(). Таким образом выполняется инициализация генератора случайных чисел. Приложение Molecule использует функцию rand() чтобы добавлять к иерархии фреймов случайные характеристики. Поскольку для инициализации генератора случайных чисел используется число, которое изменяется при каждом запуске приложения, атрибуты иерархии фреймов (такие, как оси вращения и скорость) будут меняться при каждом запуске программы.
На втором этапе создается шесть сеток. Сначала выполняется загрузка сферической сетки:
D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER meshbuilder; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL);
Для загрузки сетки из ресурсов программы используется экземпляр интерфейса Direct3DRMMeshBuilder. В дальнейшем для доступа к новой сетке используется указатель meshbuilder. Потом создаются шесть экземпляров интерфейса Direct3DRMMesh:
for (int i = 0; i < MAXDEPTH; i++) { ScaleMesh(meshbuilder, D3DVALUE(MAXDEPTH - i)); D3DCOLOR clr = meshcolor[i]; D3DVALUE r = D3DRMColorGetRed(clr); D3DVALUE g = D3DRMColorGetGreen(clr); D3DVALUE b = D3DRMColorGetBlue(clr); meshbuilder->SetColorRGB(r, g, b); meshbuilder->CreateMesh(&mesh[i]); }
Для создания сеток используется цикл. Масштаб и цвет сетки зависит от итерации цикла. Цвета каждой из создаваемых сеток содержатся в массиве meshcolor. Сразу после назначения цвета выполняется создание экземпляра интерфейса Direct3DRMMesh с помощью функции CreateMesh() интерфейса Direct3DRMMeshBuilder.
Третьим шагом является создание иерархии фреймов:
CreateHierarchy();
Функция CreateHierarchy() при создании иерархии фреймов основывается на значениях переменных класса curdepth и numchildren. Эти переменные получают свои начальные значения в конструкторе MoleculeWin, но могут быть изменены с помощью команд меню Depth и Children.
На двух заключительных этапах создается источник света и порт просмотра. Мы опустим обсуждение этих фрагментов кода, поскольку источники света были подробно рассмотрены в главе 6, а этап создания порта просмотра обсуждался в главе 4 и будет более подробно рассмотрен в главе 9.
Функция CreateHierarchy() отвечает за создание иерархии фреймов. Код этой функции выглядит следующим образом:
BOOL MoleculeWin::CreateHierarchy() { static LPDIRECT3DRMFRAME mainframe; if (mainframe) { scene->DeleteChild(mainframe); mainframe->Release(); } d3drm->CreateFrame(scene, &mainframe); for (int i = 0;i < numchildren; i++) CreateChildren(mainframe, curdepth); return TRUE; }
Функция использует статический указатель на фрейм (mainframe) для доступа к корневому фрейму иерархии. Не следует путать этот фрейм с корневым фреймом сцены (scene). Фрейм scene является корневым для всей сцены в целом. Фрейм mainframe является корневым только для иерархии фреймов.
Если указатель mainframe уже инициализирован, фрейм удаляется из сцены с помощью функции DeleteChild() интерфейса Direct3DRMFrame. Это делается, чтобы убрать со сцены предыдущую иерархию фреймов. Затем указатель mainframe инициализируется с помощью функции CreateFrame() интерфейса Direct3DRM. В качестве первого аргумента функции CreateFrame() передается указатель scene. Это указывает на то, что новый фрейм будет потомком фрейма scene.
Далее в цикле вызывается функция CreateChildren(). Число итераций цикла зависит от значения переменной класса numchildren. Функция CreateChildren() получает два аргумента: указатель на фрейм, к которому должны быть подсоединены дочерние фреймы, и целое число, указывающее желаемую глубину иерархии фреймов. Это целое число очень важно, поскольку именно оно определяет в какой момент рекурсивная функция CreateChildren() прекратит вызывать сама себя.
Функция CreateChildren() назначает атрибуты вращения, присоединяет сетки и создает дочерние фреймы.
BOOL MoleculeWin::CreateChildren(LPDIRECT3DRMFRAME frame, int depth) { LPDIRECT3DRMFRAME parent; frame->GetParent(&parent); D3DVECTOR vector; D3DRMVectorRandom(&vector); frame->SetRotation(parent, vector.x, vector.y, vector.z, D3DVALUE(rand() % 100) / D3DVALUE(1000) + D3DVALUE(.1)); frame->AddVisual(mesh[curdepth - depth]); if (depth > 1) { LPDIRECT3DRMFRAME child; d3drm->CreateFrame(frame, &child); static int count; count++; D3DVALUE trans = distance[curdepth - depth]; D3DVALUE smalltrans = trans / D3DVALUE(2); D3DVALUE xtrans = (count % 2) ? trans : -trans; D3DVALUE ytrans = (rand() % 2) ? smalltrans : -smalltrans; D3DVALUE ztrans = (rand() % 2) ? smalltrans : -smalltrans; child->SetPosition(frame, xtrans, ytrans, ztrans); for (int i = 0; i < numchildren; i++) CreateChildren(child, depth - 1); } return TRUE; }
Сначала функция CreateChildren() назначает полученному фрейму атрибут вращения. Чтобы сделать это, необходимо получить указатель на фрейм, являющийся родителем полученого фрейма. Для этой цели применяется функция GetParent() интерфейса Direct3DRMFrame. Как только родительский фрейм становится известен, выполняется вычисление и назначение атрибутов вращения:
D3DVECTOR vector; D3DRMVectorRandom(&vector); frame->SetRotation(parent, vector.x, vector.y, vector.z, D3DVALUE(rand() % 100) / D3DVALUE(1000) + D3DVALUE(.1));
Обратите внимание, что фрейм parent передается функции SetRotation() в ее первом аргументе. Однако сперва с помощью функции D3DRMVectorRandom() выполняется вычисление случайного вектора, который используется для получения следующих трех аргументов функции SetRotation(). При вычислении последнего аргумента функции SetRotation(), определяющего скорость вращения, используется функция rand(). Выражение, применяемое для вычисления скорости обеспечивает случайный выбор значения в диапазоне от медленной до средней скорости.
Затем к фрейму присоединяется соответствующая сетка:
frame->AddVisual(mesh[curdepth - depth]);
Выбор сетки зависит от глубины текущего фрейма в иерархии. Оставшаяся часть функции CreateChildren() выглядит так:
if (depth > 1) { LPDIRECT3DRMFRAME child; d3drm->CreateFrame(frame, &child); static int count; count++; D3DVALUE trans = distance[curdepth - depth]; D3DVALUE smalltrans = trans / D3DVALUE(2); D3DVALUE xtrans = (count % 2) ? trans : -trans; D3DVALUE ytrans = (rand() % 2) ? smalltrans : -smalltrans; D3DVALUE ztrans = (rand() % 2) ? smalltrans : -smalltrans; child->SetPosition(frame, xtrans, ytrans, ztrans); for (int i = 0; i < numchildren; i++) CreateChildren(child, depth - 1); }
Выполнение этой части кода зависит от значения параметра depth. Код выполняется, если значение depth больше единицы. Код создает новый дочерний фрейм с помощью функции CreateFrame() интерфейса Direct3DRM. Новый фрейм позиционируется на основе полученных псевдослучайных чисел. Статическая переменная счетчика используется, чтобы при дальнейших вычислениях избежать получения обескураживающе предсказуемых результатов. Вычисленная позиция назначается фрейму функцией SetPosition().
В завершение функция CreateChildren() вызывает сама себя. Используемый для вызова функции цикл зависит от числа дочерних фреймов, которые должны быть присоединены. Обратите внимание, что в качестве второго аргумента CreateChildren() используется выражение depth – 1. Как только значение параметра depth станет равным единице, создание дочерних фреймов прекратится. Если из аргумента, задающего глубину не вычесть единицу, функция будет бесконечно вызывать сама себя.
Меню Depth приложения Molecule предлагает шесть различных значений глубины. Для реализации каждого пункта меню применяются две функции. Функции для первого пункта меню (устанавливающего глубину иерархии равной 1), выглядят следующим образом:
void MoleculeWin::OnDepth1() { curdepth = 1; CreateHierarchy(); } void MoleculeWin::OnUpdateDepth1(CCmdUI* pCmdUI) { pCmdUI->SetCheck(curdepth == 1); }
Первая функция, OnDepth1(), вызывается, когда пользователь выбирает первый пункт в меню Depth. Она присваивает 1 переменной curdepth и вызывает функцию CreateHierarchy() чтобы заново создать всю иерархию фреймов.
Вторая функция, OnUpdateDepth1(), вызывается MFC перед отображением меню Depth. Функция SetCheck() используется для отображения флажка рядом с пунктом меню, соответствующим текущему значению глубины.
Оставшиеся десять функций меню Depth практически полностью идентичны только что рассмотренным. Их единственным отличием является число, используемое при манипуляциях с переменной класса curdepth.
Меню Children позволяет выбрать одно из четырех возможных значений, находящихся в диапазоне от одного до четырех. Подобно меню Depth для реализации каждого из пунктов меню применяются две функции. Ниже приведен код функций для первого пункта меню Children (задающего наличие у каждого фрейма одного потомка):
void MoleculeWin::OnChildren1() { numchildren = 1; CreateHierarchy(); } void MoleculeWin::OnUpdateChildren1(CCmdUI* pCmdUI) { pCmdUI->SetCheck(numchildren == 1); }
Эти функции аналогичны функциям меню Depth. Единственным отличием является использование переменной numchildren вместо curdepth.
netlib.narod.ru | < Назад | Оглавление | Далее > |