В приложении FacePick мышь используется не только для выбора граней, но и для вращения сетки и изменения ее ориентации. Для отслеживания состояния левой кнопки мыши класс FacePickWin использует функции OnLButtonDown() и OnLButtonUp(). Код функции OnLButtonDown() выглядит следующим образом:
void FacePickWin::OnLButtonDown(UINT nFlags, CPoint point) { int faceindex = PickFace(point); if (faceindex != -1) { LPDIRECT3DRMFACEARRAY facearray; meshbuilder->GetFaces(&facearray); LPDIRECT3DRMFACE face; facearray->GetElement(faceindex, &face); face->SetColor(pickcolor); face->Release(); facearray->Release(); } else if (!drag) { drag = TRUE; last_x = GetMouseX(); last_y = GetMouseY(); SetCapture(); ShowCursor(FALSE); } RMWin::OnLButtonDown(nFlags, point); }Сперва функция OnLButtonDown() вызывает функцию PickFace(), которая и реализует операцию выбора. Функция PickFace() очень похожа на функцию PickMesh() из приложения MeshPick, за исключением того, что функция PickFace() возвращает индекс выбранной грани (или –1, если ни одна грань не была выбрана).
Если грань была выбрана, ее индекс используется для изменения цвета грани. Сначала для получения массива граней используется функция GetFaces() интерфейса Direct3DRMMeshBuilder. Полученный ранее индекс используется для извлечения указателя на выбранную грань. Затем для смены цвета грани вызывается функция SetColor() интерфейса Direct3DRMFace.
Если грань не выбрана, начинается операция перетаскивания. В приложении MeshPick операция перетаскивания использовалась для переперемещения выбранной сетки. В приложении FacePick операция перетаскивания применяется для вращения сетки. В данном случае при запуске операции перетаскивания текущие координаты указателя мыши сохраняются в переменных last_x и last_y, после чего вызываются функции SetCapture() и ShowCursor().
Теперь пришло время взглянуть на функцию OnLButtonUp():
void FacePickWin::OnLButtonUp(UINT nFlags, CPoint point) { if (drag) { end_drag = TRUE; ReleaseCapture(); ShowCursor(TRUE); } RMWin::OnLButtonUp(nFlags, point); }Вы можете предположить, что функция OnLButtonUp() прекращает операцию перетаскивания (если она была начата). Однако, такое решение помешало бы реализации одной из возможностей приложения FacePick. Приложение FacePick позволяет пользователю вращать отображаемую сетку. Сетка может быть повернута в ходе операции перетаскивания, но, кроме того, может быть приведена в движение путем резкого перемещения мыши с одновременным отпусканием кнопки. Если поместить здесь код прекращения операции перетаскивания, эту возможность не удастся реализовать, поскольку код должен иметь возможность зафиксировать последние атрибуты вращения. Код установки атрибутов вращения находится в функции обратного вызова UpdateDrag(). Вместо того, чтобы дублировать этот код, мы устанавливаем флаг, сигнализирующий о завершении операции перетаскивания. Тем временем мы воосстанавливаем отображение указателя мыши и отменяем захват мыши программой, поскольку эти действия не влияют на код, связанный с атрибутами вращения.
Приложение MeshPick использует две функции обработки сообщений, чтобы реагировать на изменение состояния кнопок мыши. Функция OnLButtonDown() вызывается MFC каждый раз, когда пользователь нажимает левую кнопку мыши. Код этой функции выглядит так:
void MeshPickWin::OnLButtonDown(UINT nFlags, CPoint point) { if (PickMesh(point)) { ShowCursor(FALSE); SetCapture(); } RMWin::OnLButtonDown(nFlags, point); }MFC передает в функцию OnLButtonDown() два аргумента. Первый представляет собой набор флагов, указывающих на состояние некоторых клавиш (CTRL, SHIFT ит.д.) в момент нажатия на кнопку мыши. Второй параметр, point, содержит координаты указателя мыши в момент нажатия кнопки. В функции OnLButtonDown() параметр point используется в качестве аргумента для функции PickMesh(), которая определяет, существует ли в указанной точке какой-либо объект, и, если да, то инициализирует операцию перетаскивания. Если операция перетаскивания инициализирована, функция PickMesh() возвращает TRUE, и вызывается функция ShowCursor() чтобы скрыть указатель мыши на время операции перетаскивания. Кроме того, вызывается функция SetCapture(), чтобы уведомить Windows, что наше приложение хочет получать все сообщения от мыши, даже когда указатель мыши находится вне окна приложения. Перед выходом выполняется вызов функции OnLButtonDown() базового класса.
Функция OnLButtonUp() вызывается, когда пользователь отпускает левую кнопку мыши. Код функции приведен ниже:
Функция проверяет значение члена данных drag.frame. Эта переменная указывает, во-первых, есть ли начатая операция перетаскивания, и, во-вторых, какой фрейм перетаскивается. Член данных drag.frame инициализируется в функции PickMesh() если в точке, указанной курсором мыши, обнаружен объект.
Если значение переменной drag.frame не равно нулю, в настоящий момент выполняется операция перетаскивания, и мы должны ее прекратить (поскольку кнопка мыши была отпущена). Переменной присваивается нулевое значение, указатель мыши отображается на экране и отменяется захват мыши приложением.
Приложение Zoom позволяет во время работы выбирать тип анимационной последовательности (линейная или сплайновая) с помощью меню Animation. Для этого вызывается функция SetOptions() интерфейса Direct3DRMAnimation с различными наборами флагов. Две функции, которые отвечают за работу команд меню Animation, выглядят следующим образом:
void ZoomWin::OnAnimationLinear() { animation->SetOptions(D3DRMANIMATION_LINEARPOSITION | D3DRMANIMATION_CLOSED | D3DRMANIMATION_POSITION); } void ZoomWin::OnAnimationSpline() { animation->SetOptions(D3DRMANIMATION_SPLINEPOSITION | D3DRMANIMATION_CLOSED | D3DRMANIMATION_POSITION); }Поддержка меню Animation включает также отображение флажка слева от пункта меню, соответствующего выбранному в данный момент режиму анимации. Две функции, отвечающие за вывод флажков рядом с пунктами меню выглядят следующим образом:
void ZoomWin::OnUpdateAnimationLinear(CCmdUI* pCmdUI) { D3DRMANIMATIONOPTIONS options = animation->GetOptions(); pCmdUI->SetCheck(options & D3DRMANIMATION_LINEARPOSITION); } void ZoomWin::OnUpdateAnimationSpline(CCmdUI* pCmdUI) { D3DRMANIMATIONOPTIONS options = animation->GetOptions(); pCmdUI->SetCheck(options & D3DRMANIMATION_SPLINEPOSITION); }Для получения текущих параметров анимации эти функции вызывают функцию GetOptions() интерфейса Direct3DRMAnimation. Функция SetCheck() используется для установки флажка рядом с пунктом меню, соответствующим активному в данный момент режиму.
Приложение FacePick поддерживает два диалоговых окна для выбора цвета. Этими диалоговыми окнами управляют функции OnColorsFace() и OnColorsMesh(). Функция OnColorsFace() предоставляет пользователю возможность выбрать цвет, в который будет окрашена выбранная грань. Функция OnColorsMesh() позволяет выбрать цвет, в который будет окрашена вся сетка. Обе функции используют класс MFC CColorDialog. Код функции OnColorsFace() выглядит так:
void FacePickWin::OnColorsFace() { CColorDialog dialog(0, CC_RGBINIT); dialog.m_cc.rgbResult = D3DCOLOR_2_COLORREF(pickcolor); if (dialog.DoModal() == IDOK) { COLORREF clr = dialog.GetColor(); pickcolor = COLORREF_2_D3DCOLOR(clr); } }Класс CColorDialog позволяет указать цвет, который будет выбран при появлении диалогового окна на экране. Благодаря этому, мы можем вывести диалоговое окно, в котором уже будет выбран текущий цвет выбираемых граней (естественно, это работает, только если указанный цвет является одним из отображаемых в диалоговом окне цветов).
Константа CC_RGBINIT используется в качестве аргумента конструктора диалогового окна, чтобы указать, что мы задаем цвет по умолчанию. Цвет по умолчанию должен быть присвоен члену данных m_cc.rgbResult. Переменная pickcolor является членом данных класса MeshPickWin, в котором хранится текущий цвет окраски выбранных граней. Член данных pickcolor относится к типу D3DCOLOR, а для диалогового окна необходимо, чтобы цвета были представлены в формате COLORREF, и чтобы присваивание было выполнено правильно, требуется функция преобразования. Для этих целей класс RMWin предоставляет функции D3DCOLOR_2_COLORREF() и COLORREF_2_D3DCOLOR() (см. главу4).
Чтобы открывшееся окно диалога было модальным, мы используем функцию DoModal(). Если пользователь выходит из окна диалога любым способом, отличающимся от щелчка по кнопке OK, функция завершает работу не выполняя больше никаких действий. Если возвращена константа IDOK, новый цвет присваивается члену данных pickcolor.
Функция OnColorsMesh() очень похожа:
void FacePickWin::OnColorsMesh() { CColorDialog dialog; if (dialog.DoModal() == IDOK) { COLORREF clr = dialog.GetColor(); D3DCOLOR meshcolor = COLORREF_2_D3DCOLOR(clr); meshbuilder->SetColor(meshcolor); } }В отличие от функции OnColorsFace(), в функции OnColorsMesh() не указывается выбираемый по умолчанию в диалоговом окне цвет. Если пользователь щелкает по кнопке OK, новый цвет извлекается из класса диалогового окна с помощью функции GetColor(). Полученное значение типа COLORREF преобразуется функцией COLORREF_2_D3DCOLOR() и устанавливается функцией SetColor() интерфейса Direct3DRMMeshBuilder.
Приложение FacePick позволяет загружать и сохранять сетки с помощью меню File. Для этих целей в класс FacePickWin включены функции OnFileOpen() и OnFileSave(). Функция OnFileOpen() выглядит следующим образом:
void FacePickWin::OnFileOpen() { static char BASED_CODE filter[] = "X Files (*.x)|*.x|All Files (*.*)|*.*||"; CFileDialog opendialog(TRUE, 0, 0, OFN_FILEMUSTEXIST, filter, this); if (opendialog.DoModal() == IDOK) { CWaitCursor cur; CString filename = opendialog.GetPathName(); LPDIRECT3DRMMESHBUILDER builder; d3drm->CreateMeshBuilder(&builder); HRESULT r = builder->Load((void*)(LPCTSTR)filename, NULL, D3DRMLOAD_FROMFILE, NULL, NULL); if (r != D3DRM_OK) { CString msg; msg.Format("Failed to load file\n'%s'", filename); AfxMessageBox(msg); return; } meshframe->DeleteVisual(meshbuilder); meshbuilder->Release(); meshbuilder = builder; meshframe->AddVisual(meshbuilder); meshscale = ScaleMesh(meshbuilder, D3DVALUE(25)); } }Функция использует класс MFC CFileDialog. Обратите внимание, что в качестве аргумента конструктора класса используется строка filter. Эта строка информирует диалоговое окно о том, какие типы файлов могут быть загружены.
Для вывода диалогового окна используется функция DoModal(). Если будет возвращена константа IDOK, имя файла извлекается из класса диалогового окна с помощью функции GetPathName(). Обратите внимание, что мы не проверяем, существует ли данный файл. Это сделано потому, что при конструировании объекта диалогового окна мы указали константу OFN_FILEMUSTEXIST. Данная константа указывает классу диалогового окна, что в случае ручного ввода имени файла должен быть проверен факт существования данного файла. В результате диалоговое окно просто не позволит ввести имя несуществующего файла (эта проверка не гарантирует правильности содержимого файла, она гарантирует только его существование).
Затем функция пытается загрузить новую сетку. Если попытка завершается неудачно, выводится окно с сообщением об ошибке и функция завершает работу. Эта ошибка не вызовет проблем в работе приложения, просто и дальше будет отображаться существующая в сцене сетка.
Если новая сетка успешно загружена, существующая сетка удаляется из сцены функцией DeleteVisual() интерфейса Direct3DRMFrame, и указатель meshbuilder освобождается. Затем к сцене добавляется новый конструктор сеток с помощью функции AddVisual().
На последнем этапе вызывается функция ScaleMesh(). Мы видели, как эта функция используется в других демонстрационных программах, но в данном случае есть несколько отличий. Вспомните, что функция ScaleMesh() масштабирует сетку до желаемого размера. В данном случае мы масштабируем любую загруженную сетку до размера в 25 единиц. Это замечательно работало в других приложениях, и работает здесь, но если после масштабирования сетка будет записана на диск, ее размеры изменятся. Поэтому перед тем как записывать сетку на диск, ей необходимо вернуть первоначальные размеры. Функция ScaleMesh() возвращает значение, являющееся коэффициентом, использованным для масштабирования оригинальной сетки. Сохранение этого значения позволит нам вернуть сетке оригинальный размер. Мы увидим, как это делается, когда будем обсуждать код функции OnFileSave().
Функция OnFileSave() определена следующим образом:
void FacePickWin::OnFileSave() { static char BASED_CODE filter[] = "X Files (*.x)|*.x||"; CFileDialog opendialog(FALSE, ".x", "", OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, filter); if (opendialog.DoModal() == IDOK) { CWaitCursor cur; CString filename = opendialog.GetPathName(); D3DVALUE restorescale = D3DVALUE(1)/meshscale; meshbuilder->Scale(restorescale, restorescale, restorescale); meshbuilder->Save(filename, D3DRMXOF_BINARY, D3DRMXOFSAVE_ALL); meshbuilder->Scale(meshscale, meshscale, meshscale); } }Функция OnFileSave() также использует класс CFileDialog, но в вызове конструктора класса диалогового окна указан другой набор констант. Константа OFN_HIDEREADONLY указывает, что в диалоговом окне не надо отображать флажок Open as Read Only. Наличие константы OFN_OVERWRITEPROMPT приведет к тому, что в случае перезаписи существующего файла программа попросит подтвердить необходимость выполнения данного действия.
Для вывода диалогового окна используется функция DoModal(). Если функция возвращает константу IDOK, текущая сетка записывается на диск. Класс CWaitCursor используется для отображения во время выполнения записи указателя мыши в форме песочных часов. Для получения имени сохраняемого или перезаписываемого файла применяется функция GetPathName(). Затем сетке возвращается ее оригинальный размер. Значение restorescale вычисляется таким образом, чтобы обратить операцию масштабирования, которой сетка подверглась при загрузке. Затем используется функция Scale() интерфейса Direct3DRMMeshBuilder чтобы масштабировать сетку согласно полученному коэффициенту restorescale. Для сохранения сетки вызывается функция Save(). Затем сетке возвращается ее предыдущий размер. Если не сделать этого, то после сохранения отображаемая сетка может стать огромной или микроскопической.
Приложение MultiView предоставляет команды меню, позволяющие настраивать любой из портов просмотра. Для каждого из портов просмотра предусмотрено отдельное меню. Здесь мы обсудим обработчики сообщений для меню первого порта просмотра. Код для оставшихся портов просмотра практически идентичен приведенному.
void RMWin::OnViewport1Disabled() { view1setting = VIEWPORT_DISABLED; viewport1->Clear(); } void RMWin::OnViewport1Front() { view1setting = VIEWPORT_FRONT; ConfigViewport(camera1, view1setting); } void RMWin::OnViewport1Left() { view1setting = VIEWPORT_LEFT; ConfigViewport(camera1, view1setting); } void RMWin::OnViewport1Right() { view1setting = VIEWPORT_RIGHT; ConfigViewport(camera1, view1setting); } void RMWin::OnViewport1Top() { view1setting = VIEWPORT_TOP; ConfigViewport(camera1, view1setting); }Каждая из функций присваивает свое значение члену данных view1setting. Функция OnViewportDisabled() использует функцию Clear() интерфейса Direct3DRMViewport для очистки порта просмотра. В оставшихся функциях используется функция ConfigViewport() для настройки порта просмотра в соответствии с новыми параметрами.
Сцена приложения FacePick конструируется в функции FacePickWin::CreateScene(), код которой показан в листинге9.3.
Листинг 9.3. Функция FacePickWin::CreateScene() |
BOOL FacePickWin::CreateScene() { // ------- СЕТКА -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_D3DMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->SetQuality(D3DRMRENDER_FLAT); ScaleMesh(meshbuilder, D3DVALUE(25)); //------- ФРЕЙМ ------ d3drm->CreateFrame(scene, &meshframe); meshframe->SetRotation(scene, D3DVALUE(1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(.05)); meshframe->AddVisual(meshbuilder); meshframe->AddMoveCallback(UpdateDrag, NULL); // --------- СВЕТ -------- LPDIRECT3DRMLIGHT dlight; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &dlight); LPDIRECT3DRMLIGHT alight; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(0.40), D3DVALUE(0.40), D3DVALUE(0.40), &alight); LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->AddLight(dlight); lightframe->AddLight(alight); lightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); 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() выполняет четыре действия:
Создание сетки. Создание фрейма для сетки. Создание двух источников света. Создание порта просмотра.Код, выполняющий первое действие, выглядит так:
D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_D3DMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->SetQuality(D3DRMRENDER_FLAT); ScaleMesh(meshbuilder, D3DVALUE(25));Используемая по умолчанию сетка хранится в ресурсах приложения и идентифицируется константой IDR_D3DMESH. Хотя функция CreateScene() загружает эту внутреннюю сетку автоматически, сетка может быть заменена с помощью команды Open меню File. Обратите внимание, что функция SetQuality() используется, чтобы изменить метод визуализации сетки на плоский. Плоский метод выбран потому, что при его использовании отдельные грани сетки выделяются более четко, чем в других методах.
На втором этапе выполняется создание фрейма для сетки:
d3drm->CreateFrame(scene, &meshframe); meshframe->SetRotation(scene, D3DVALUE(1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(.05)); meshframe->AddVisual(meshbuilder); meshframe->AddMoveCallback(UpdateDrag, NULL);Указатель meshframe является членом класса FacePickWin и поэтому не объявлен в функции CreateScene(). Инициализацию указателя выполняет функция CreateFrame() интерфейса Direct3DRM. Функция SetRotation() назначает фрейму начальные атрибуты вращения. Эти атрибуты могут быть в любой момент изменены путем перетаскивания сетки с помощью мыши (мы увидим как это делается, когда будем обсуждать функцию UpdateDrag()). После вызова функции SetRotation(), к новому фрейму присоединяется созданная ранее сетка с помощью функции AddVisual(). Затем вызывается функция AddMoveCallback() для установки функции обратного вызова UpdateDrag().
На следующем этапе создаются два источника света:
LPDIRECT3DRMLIGHT dlight; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &dlight); LPDIRECT3DRMLIGHT alight; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(0.40), D3DVALUE(0.40), D3DVALUE(0.40), &alight); LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->AddLight(dlight); lightframe->AddLight(alight); lightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0));Источники направленного и рассеянного света создаются функцией CreateLightRGB() интерфейса Direct3DRM. Источник рассеянного света будет испускать серый свет, поскольку при его создании были указаны уменьшенные значения RGB. Потом создается фрейм и источники света присоединяются к нему с помощью функции AddLight(). В завершение вызывается функция SetOrientation(), чтобы задать ориентацию источника направленного света. На источник рассеянного света ориентация фрейма не оказывает никакого влияния.
На завершающем, четвертом этапе создается порт просмотра:
d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);Указатель camera инициализируется функцией CreateFrame() интерфейса Direct3DRM, а затем его местоположение задается функцией SetPosition(). Созданный фрейм используется в качестве аргумента функции CreateViewport() интерфейса Direct3DRM.
Функция PickFace() использует функцию Pick() интерфейса Direct3DRMViewport для выполнения операции выбора. Если в результате выполнения операции выбора был возвращен объект, функция вернет индекс грани этого объекта. Код функции PickFace() выглядит следующим образом:
int FacePickWin::PickFace(const CPoint& point) { HRESULT r; LPDIRECT3DRMPICKEDARRAY pickarray; viewport->Pick(point.x, point.y, &pickarray); int faceindex = -1; DWORD numpicks = pickarray->GetSize(); if (numpicks > 0) { LPDIRECT3DRMVISUAL visual; LPDIRECT3DRMFRAMEARRAY framearray; D3DRMPICKDESC pickdesc; r = pickarray->GetPick(0, &visual, &framearray, &pickdesc); if (r == D3DRM_OK) { faceindex = pickdesc.ulFaceIdx; visual->Release(); framearray->Release(); } } pickarray->Release(); return faceindex; }Сперва вызывается функция Pick() интерфейса Direct3DRMViewport. В первых двух аргументах этой функции передаются текущие координаты указателя мыши. Третий аргумент— это адрес указателя на интерфейс Direct3DRMPickedArray.
Затем вызывается функция GetSize(), чтобы определить, был ли выбран какой-либо объект. Если массив пуст, указатель pickarray освобождается и функция возвращает –1. Если в массиве есть элементы, первый из них извлекается с помощью функции GetPick(). Нам необходим только первый элемент, поскольку элементы массива отсортированы по значению координаты Z, а нас интересует самый близкий к зрителю объект.
Функция GetPick() инициализирует два указателя и структуру. Первый указатель указывает на видимый объект, который был выбран. В нашем случае указатель visual будет указывать на сетку, созданную в функции CreateScene() (поскольку эта сетка является единственным видимым объектом сцены). Однако указатель на видимый объект нас не интересует. Он мог бы потребоваться, если бы сцена содержала несколько сеток (как в приложении MeshPick). Второй указатель, инициализируемый функцией GetPick() — это указаетль на массив фреймов. Эти данные нас также не интересуют, по рассмотренным выше причинам.
Данные, в которых мы нуждаемся, — это индекс выбранной грани. Функция GetPick() сохраняет это значение в поле ulFaceIdx структуры D3DRMPICKDESC. Функция PickFace() сохраняет это значение и возвращает его после освобождения своих локальных указателей, завершая тем самым свою работу.
Между прочим, структура D3DRMPICKDESC содержит еще два поля, которые могут оказаться полезными:
lGroupIdx: Содержит индекс группы граней сетки. Интерфейс Direct3DRMMesh поддерживает работу с несколькими группами граней в сетке. Индекс группы идентифицирует группу, в которую входит выбранная грань. vPosition: Ориентация грани. Этот вектор указывает лицевое направление выбранной грани.Функция UpdateDrag() является функцией обратного вызова, устанавливаемой в функции CreateScene(). Функция UpdateDrag() отвечает за вычисление новых атрибутов вращения сетки во время операции перетаскивания. Код функции приведен ниже:
void FacePickWin::UpdateDrag(LPDIRECT3DRMFRAME frame, void*, D3DVALUE) { if (drag) { double delta_x = GetMouseX() - last_x; double delta_y = GetMouseY() - last_y; last_x = GetMouseX(); last_y = GetMouseY(); double delta_r = sqrt(delta_x * delta_x + delta_y * delta_y); double radius = 50; double denom = sqrt(radius * radius + delta_r * delta_r); if (!(delta_r == 0 || denom == 0)) frame->SetRotation(0, D3DDivide(-delta_y, delta_r), D3DDivide(-delta_x, delta_r), D3DVALUE(0.0), D3DDivide(delta_r, denom)); } if (end_drag) { drag = FALSE; end_drag = FALSE; } }Для вычисления вектора и скорости вращения функция использует текущую позицию указателя мыши и позицию указателя мыши в момент начала операции перетаскивания. Говоря по существу, перемещение мыши в двух измерениях преобразуется в вектор, а разница между старыми и новыми координатами мыши используется для вычисления скорости вращения. Полученные значения устанавливаются с помощью функции SetRotation() интерфейса Direct3DRMFrame.
Обратите внимание, что при каждом вызове функции UpdateDrag() проверяется флаг end_drag. Этот флаг устанавливается в функции, чтобы указать, что операция перетаскивания должна быть завершена. Если флаг end_drag установлен, операция перетаскивания прерывается.
Функция CreateScene() приложения MeshPick привелена в листинге 9.2.
Листинг 9.2. Функция MeshPickWin::CreateScene() |
BOOL MeshPickWin::CreateScene() { // ------- КОНСТРУКТОР СЕТОК -------- 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); meshbuilder->SetQuality(D3DRMRENDER_FLAT); //------ ДЕВЯТЬ СЕТОК ---------- for (int x = 0; x < 3; x++) { for (int y = 0; y < 3; y++) { LPDIRECT3DRMMESH mesh; meshbuilder->CreateMesh(&mesh); mesh->SetGroupColorRGB(0, D3DVALUE(x % 2), D3DVALUE(y % 2), D3DVALUE(1)); LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(mesh); int xoffset = (rand() % 3) - 1; int yoffset = (rand() % 3) - 1; meshframe->SetPosition(scene, D3DVALUE((x - 1) * 10 + xoffset), D3DVALUE((y - 1) * 10 + yoffset), D3DVALUE(0)); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.1)); meshframe->Release(); meshframe = 0; mesh->Release(); mesh = 0; } } meshbuilder->Release(); meshbuilder = 0; //------- ФУНКЦИЯ ОБРАТНОГО ВЫЗОВА -------- scene->AddMoveCallback(UpdateDrag, NULL); // -------- НАПРАВЛЕННЫЙ СВЕТ -------- 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() выполняет следующие пять действий:
Использует интерфейс Direct3DRMMeshBuilder для загрузки сферической сетки. Создает девять сеток и фреймы для каждой из них. Устанавливает функцию обратного вызова UpdateDrag(). Создает источник света. Создает порт просмотра.На первом этапе осуществляется создание конструктора сеток:
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); meshbuilder->SetQuality(D3DRMRENDER_FLAT);Сетка загружается из ресурсов приложения функцией Load() интерфейса Direct3DRMMeshBuilder. Затем вызывается функция SetQuality(), чтобы изменить используемый по умолчанию для конструктора сеток метод визуализации Гуро на плоский метод визуализации.
На втором этапе для создания девяти сеток используется цикл. В цикле используется инициализированный ранее указатель meshbuilder:
for (int x = 0; x < 3; x++) { for (int y = 0; y < 3; y++) { LPDIRECT3DRMMESH mesh; meshbuilder->CreateMesh(&mesh); mesh->SetGroupColorRGB(0, D3DVALUE(x % 2), D3DVALUE(y % 2), D3DVALUE(1)); LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(mesh); int xoffset = (rand() % 3) - 1; int yoffset = (rand() % 3) - 1; meshframe->SetPosition(scene, D3DVALUE((x - 1) * 10 + xoffset), D3DVALUE((y - 1) * 10 + yoffset), D3DVALUE(0)); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.1)); meshframe->Release(); meshframe = 0; mesh->Release(); mesh = 0; } } meshbuilder->Release(); meshbuilder = 0;Для создания девяти сеток применяются вложенные циклы. В теле внутреннего цикла для создания сетки используется функция CreateMesh() интерфейса Direct3DRMMeshBuilder. Назначаемый сетке цвет зависит от текущей итерации цикла. Затем создается фрейм и сетка присоединяется к нему с помощью функции AddVisual(). Местоположение сетки зависит от текущей итерации цикла, но слегка изменяется на случайную величину (это сделано для того, чтобы пользователь понял, что сетки можно перемещать). Каждому фрейму назначаются атрибуты вращения, после чего указатели meshframe и mesh освобождаются.
Затем устанавливается функция обратного вызова:
scene->AddMoveCallback(UpdateDrag, NULL);При установке функции обратного вызова используется фрейм scene (корневой фрейм). Этот фрейм выбран произвольно, можно использовать любой другой фрейм сцены.
На четвертом этапе выполняется создание источников света:
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;Приведенный выше код создает направленный источник света и позиционирует его таким образом, чтобы свет распространялся между положительным направлением оси Z и отрицательным направлением оси Y. Источник света присоединаяется к фрейму с помощью функции AddLight() интерфейса Direct3DRMFrame.
На последнем этапе осуществляется создание порта просмотра:
d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);Указатель на фрейм camera инициализируется функцией CreateFrame() интерфейса Direct3DRM, после чего фрейм позиционируется с помощью функции SetPosition() интерфейса Direct3DRMFrame. Указатель viewport инициализируется функцией CreateViewport() интерфейса Direct3DRM.
Как мы отмечали при рассмотрении функции OnLButtonDown(), функция PickMesh() используется для проверки наличия объекта в указанной точке:
BOOL MeshPickWin::PickMesh(const CPoint& point) { HRESULT r; LPDIRECT3DRMPICKEDARRAY pickarray; viewport->Pick(point.x, point.y, &pickarray); BOOL ret = FALSE; DWORD numpicks = pickarray->GetSize(); if (numpicks > 0) { LPDIRECT3DRMVISUAL visual; LPDIRECT3DRMFRAMEARRAY framearray; D3DRMPICKDESC pickdesc; r = pickarray->GetPick(0, &visual, &framearray, &pickdesc); if (r == D3DRM_OK) { framearray->GetElement(framearray->GetSize() - 1, &drag.frame); D3DVECTOR pos; drag.frame->GetPosition(0, &pos); drag.origx = pos.x; drag.origy = pos.y; drag.mousedown.x = point.x; drag.mousedown.y = point.y; visual->Release(); framearray->Release(); ret = TRUE; } } pickarray->Release(); return ret; }Сначала функция MeshPick() вызывает функцию Pick() интерфейса Direct3DRMViewport. Функции Pick() передается три аргумента. Первые два аргумента определяют точку порта просмотра, которая должна быть проверена. Третий аргумент — это указатель на интерфейс Direct3DRMPickedArray. Этот указатель инициализируется массивом объектов, которые обнаружены в данной точке порта просмотра (даже если один объект полностью скрыт другими).
Интерфейс Direct3DRMPickedArray поддерживает два метода: GetSize() и GetPick(). Функция GetSize() возвращает количество элементов массива. Функция GetPick() возвращает указатель на видимый объект, который был выбран, и указатель на массив указателей на инетрфейс Direct3DRMFrame.
Массив фреймов, возвращаемый функцией GetPick() является списком всех выбранных фреймов объекта, начиная с корневого фрейма сцены. Последний фрейм в списке — это тот фрейм, к которому присоединен объект. Получив этот массив, мы используем функцию GetElement(), чтобы получить указатель на последний фрейм в массиве.
Как только будет определено, что должна начаться новая операция перетаскивания, будет выполнено присваивание значений членам структуры типа DragData. Член drag.frame используется для хранения указателя на перетаскиваемый фрейм. Местоположение фрейма и координаты указателя мыши в момент нажатия кнопки также сохраняются в структуре. Эти данные потребуются позднее, когда мы будем рассчитывать новую позицию фрейма на основе перемещения мыши.
После того, как структура с необходимыми для операции перетаскивания данными готова, указатели на различные интерфейсы освобождаются. Если был выбран какой-либо объект, функция MeshPick() возвращает TRUE.
Функция UpdateDrag() — это функция обратного вызова, устанавливаемая в функции CreateScene(). Она используется для опроса состояния приложения и перемещения сеток, когда существует начатая операция перетаскивания.
void MeshPickWin::UpdateDrag(LPDIRECT3DRMFRAME frame, void*, D3DVALUE) { if (drag.frame) { int x = GetMouseX(); int y =GetMouseY(); D3DVALUE newx = -D3DVALUE(drag.mousedown.x - x) * D3DVALUE(.07) + drag.origx; D3DVALUE newy = D3DVALUE(drag.mousedown.y - y) * D3DVALUE(.07) + drag.origy; drag.frame->SetPosition(0, newx, newy, D3DVALUE(0)); } }Функция UpdateDrag() проверяет значение переменной drag.frame, чтобы определить, существует ли фрейм (и объект, присоединенный к этому фрейму), который в данный момент перетаскивается. Если да, то для получения текущего положения указателя мыши используются функции GetMouseX() и GetMouseY() (эти функции унаследованы от класса RMWin).
На основе полученных координат указателя мыши, координат указателя мыши в момент начала операции перетаскивания и начального местоположения фрейма рассчитывается новое местоположение фрейма. Для изменения местоположения фрейма используется функция SetPosition() интерфейса Direct3DRMFrame.
Сцена для приложения MultiView конструируется в функции CreateScene(), код которой приведен в листинге9.4.
Листинг 9.4. Функция MultiViewWin::CreateScene() |
BOOL MultiViewWin::CreateScene() { // ------- СЕТКА -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_MESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); ScaleMesh(meshbuilder, D3DVALUE(30)); //------- ФРЕЙМ СЕТКИ ------ d3drm->CreateFrame(scene, &meshframe); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.1)); meshframe->AddVisual(meshbuilder); meshframe->Release(); // --------- СВЕТ -------- LPDIRECT3DRMLIGHT dlight; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &dlight); LPDIRECT3DRMLIGHT alight; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(0.40), D3DVALUE(0.40), D3DVALUE(0.40), &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; return TRUE; } |
Функция CreateScene() выполняет следующие действия:
Создание сетки. Создание фрейма для сетки. Создание и размещение двух источников света.Обратите внимание, что порты просмотра не создаются. Как вы увидите, код, относящийся к портам просмотра, был перемещен в класс RMWin. Функция CreateScene() подготавливает сцену, но не определяет, каким образом эта сцена будет показана зрителю.
Сначала выполняется создание сетки. Интерфейс Direct3DRMMeshBuilder используется для загрузки сетки из ресурсов приложения. Функция ScaleMesh() используется для изменения размеров сетки, если это необходимо.
На следующем этапе создается фрейм для сетки. С помощью функции SetRotation() фрейму назначаются атрибуты вращения, после чего к нему присоединяется созданная ранее сетка с помощью функции AddVisual().
Затем создаются два источника света и присоединяются к собственному фрейму.
Функция ConfigViewport() получает два аргумента: указатель на интерфейс Direct3DRMFrame и целое число, указывающее желаемую позицию фрейма. Код функции выглядит так:
void RMWin::ConfigViewport(LPDIRECT3DRMFRAME camera, int view) { if (view == VIEWPORT_FRONT) { camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); camera->SetOrientation(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); } else if (view == VIEWPORT_LEFT) { camera->SetPosition(scene, D3DVALUE(-50), D3DVALUE(0), D3DVALUE(0)); camera->SetOrientation(scene, D3DVALUE(1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); } else if (view == VIEWPORT_RIGHT) { camera->SetPosition(scene, D3DVALUE(50), D3DVALUE(0), D3DVALUE(0)); camera->SetOrientation(scene, D3DVALUE(-1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); } else if (view == VIEWPORT_TOP) { camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(50), D3DVALUE(0)); camera->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(0), D3DVALUE(1)); } }Для позиционирования фрейма используются функции SetPosition() и SetOrientation(). Местоположение и ориентация зависят от значения параметра view.
Функция ConfigViewport() используется как в функции CreateDevice() (что мы уже видели) так и в обработчиках сообщений команд меню Viewport (что мы увидим чуть позже).
Изменения, внесенные в класс RMWin, не ограничиваются добавлением новых переменных и функций. Кроме того, был изменен ряд функций, одна из которых— CreateDevice(). Функция CreateDevice() отвечает за создание нескольких ключевых элементов программ, использующих Direct3D. Версия CreateDevice() используемая в приложении MultiView представлена в листинге 9.6.
Листинг 9.6. Функция RMWin::CreateDevice() |
BOOL RMWin::CreateDevice() { HRESULT r; r = DirectDrawCreateClipper(0, &clipper, NULL); if (r != D3DRM_OK) { AfxMessageBox("DirectDrawCreateClipper() failed"); return FALSE; } r = clipper->SetHWnd(NULL, m_hWnd); if (r != DD_OK) { AfxMessageBox("clipper->SetHWnd() failed"); return FALSE; } RECT rect; ::GetClientRect(m_hWnd, &rect); r = d3drm->CreateDeviceFromClipper(clipper, GetGUID(), rect.right, rect.bottom, &device); if (r != D3DRM_OK) { AfxMessageBox("CreateDeviceFromClipper() failed"); return FALSE; } device->SetQuality(D3DRMRENDER_GOURAUD); HDC hdc = ::GetDC(m_hWnd); int bpp = ::GetDeviceCaps(hdc, BITSPIXEL); ::ReleaseDC(m_hWnd, hdc); switch (bpp) { case 1: device->SetShades(4); d3drm->SetDefaultTextureShades(4); device->SetDither(TRUE); break; case 8: // ... break; case 16: device->SetShades(32); d3drm->SetDefaultTextureColors(64); d3drm->SetDefaultTextureShades(32); device->SetDither(FALSE); break; case 24: case 32: device->SetShades(256); d3drm->SetDefaultTextureColors(64); d3drm->SetDefaultTextureShades(256); device->SetDither(FALSE); break; } d3drm->CreateFrame(NULL, &scene); if (CreateScene() == FALSE) { AfxMessageBox("CreateScene() failed"); return FALSE; } d3drm->CreateFrame(scene, &camera1); ConfigViewport(camera1, view1setting); d3drm->CreateFrame(scene, &camera2); ConfigViewport(camera2, view2setting); d3drm->CreateFrame(scene, &camera3); ConfigViewport(camera3, view3setting); CreateViewports(); return TRUE; } |
Вместо того, чтобы обсуждать весь код функции, мы сосредоточимся на внесенных изменениях. Нас интересует завершающая часть кода функции, следующая за инициализацией указателя на фрейм scene:
if (CreateScene() == FALSE) { AfxMessageBox("CreateScene() failed"); return FALSE; } d3drm->CreateFrame(scene, &camera1); ConfigViewport(camera1, view1setting); d3drm->CreateFrame(scene, &camera2); ConfigViewport(camera2, view2setting); d3drm->CreateFrame(scene, &camera3); ConfigViewport(camera3, view3setting); CreateViewports();Данная часть кода начинается с вызова функции CreateScene(). Если функция CreateScene() возвращает FALSE, выводится окно с сообщением об ошибке и функция CreateDevice() также возвращает FALSE.
Если функция CreateScene() завершается успешно, функция CreateDevice() инициализирует три фрейма: camera1, camera2 и camera3. Эти фреймы используются для создания и размещения трех используемых в приложении портов просмотра. После создания каждого из фреймов, он передается функции ConfigViewport() вместе с целым числом, хранящим конфигурацию порта просмотра. Функция ConfigViewport() позиционирует указанный фрейм, согласно значению, переданному во втором аргументе. При инициализации эти значения устанавливаются следующим образом:
view1setting = VIEWPORT_FRONT; view2setting = VIEWPORT_LEFT; view3setting = VIEWPORT_TOP;Данные значения указывают, что первый порт просмотра будет отображать вид на сцену спереди. Второй порт просмотра будет отображать вид на сцену слева, а третий порт просмотра будет выводить вид сверху.
После инициализации всех трех фреймов и вызова для каждого из них функции ConfigViewport(), выполняется действительное создание портов просмотра функцией CreateViewports().
Функция CreateViewports() создает три используемых в приложении порта просмотра:
void RMWin::CreateViewports() { int newwidth = device->GetWidth(); int newheight = device-7gt;GetHeight(); int onethird = newwidth / 3; int halfheight = newheight / 2; d3drm->CreateViewport(device, camera1, 0, 0, onethird * 2, newheight, &viewport1); d3drm->CreateViewport(device, camera2, onethird * 2, 0, onethird, halfheight, &viewport2); d3drm->CreateViewport(device, camera3, onethird * 2, halfheight, onethird, halfheight, &viewport3); }Функция делит доступное для вывода изображений пространство устройства на три части. Первый порт просмотра занимает первые две трети пространства устройства, а оставшиеся два порта просмотра делят оставшуюся треть. Каждый порт просмотра создается функцией CreateViewport() интерфейса Direct3DRM.
Функция OnSize() вызывается при изменении размеров окна. Это важно, поскольку размер устройства Direct3D не может быть изменен. Следовательно, в случае изменения размеров окна функция OnSize() должна уничтожить устройство и создать его заново. Поскольку порты просмотра присоединены к устройству, они также должны быть уничтожены и созданы заново. Функция OnSize() приложения MultiView выглядит следующим образом:
void RMWin::OnSize(UINT type, int cx, int cy) { CFrameWnd::OnSize(type, cx, cy); if (!device) return; int newwidth = cx; int newheight = cy; if (newwidth && newheight) { int old_dither = device->GetDither(); D3DRMRENDERQUALITY old_quality = device->GetQuality(); int old_shades = device->GetShades(); viewport1->Release(); viewport2->Release(); viewport3->Release(); device->Release(); d3drm->CreateDeviceFromClipper(clipper, GetGUID(), newwidth, newheight, &device); device->SetDither(old_dither); device->SetQuality(old_quality); device->SetShades(old_shades); CreateViewports(); } }Сначала функция сохраняет текущие параметры устройства. Эти параметры будут использованы позднее для настройки вновь созданного устройства. Для получения параметров устройства применяются функции GetDither(), GetQuality() и GetShades() интерфейса Direct3DRMDevice.
Затем освобождаются все три порта просмотра и устройство. После этого создается новое устройство функцией CreateDeviceFromClipper() интерфейса Direct3DRM. Новое устройство конфигурируется с учетом сохраненных ранее параметров.
Обратите внимание, что нет необходимости уничтожать и создавать три фрейма портов просмотра. Функция CreateViewports() (последняя функция, вызываемая в функции OnSize()) будет использовать при создании и размещении новых портов просмотра существующие фреймы.
Мы завершили обсуждение кода, который создает и конфигурирует сцену приложения и внутренние компоненты. Теперь посмотрим, что происходит после инициализации.
Важным действием является обновление сцены и визуализация выходных данных во время работы приложения. В других приложениях эту задачу выполняла функция RMApp::OnIdle(). Код функции OnIdle(), используемой в предыдущих приложениях, выглядел следующим образом:
BOOL RMApp::OnIdle(LONG count) { ASSERT(RMWin::d3drm); ASSERT(rmwin); rmwin->OnIdle(count); RMWin::d3drm->Tick(D3DVALUE(1)); return TRUE; }Функция Tick() интерфейса Direct3DRM использовалась для обновления данных программы и формирования нового изображения с учетом произошедших изменений. Этот метод прекрасно работал, когда у нас был один порт просмотра, но теперь нам требуется больший контроль. В приложении MultiView используется следующая версия функции RMApp::OnIdle():
BOOL RMApp::OnIdle(LONG lCount) { ASSERT(rmwin); rmwin->Render(); return TRUE; }Эта версия передает отвественность за обновление данных программы функции RMWin::Render(), которая выглядит следующим образом:
void RMWin::Render() { scene->Move(D3DVALUE(1.0)); if (view1setting != VIEWPORT_DISABLED) { viewport1->Clear(); viewport1->Render(scene); } if (view2setting != VIEWPORT_DISABLED) { viewport2->Clear(); viewport2->Render(scene); } if (view3setting != VIEWPORT_DISABLED) { viewport3->Clear(); viewport3->Render(scene); } device->Update(); }Вспомните, что функция Tick() интерфейса Direct3DRM выполняет как обновление данных программы, так и формирование нового изображения. Поскольку мы не можем использовать функцию Tick(), нам придется выполнять эти задачи по отдельности и самостоятельно.
Сначала для обновления данных программы вызывается функция Move() интерфейса Direct3DRMFrame. Она применяет атрибуты движения и вызывает функции обратного вызова иерархии фреймов. Мы используем корневой фрейм сцены (scene), поэтому данный вызов функции гарантирует обновление всей сцены.
Затем необходимо создать новое изображение. Это делается с помощью функций Clear() и Render() интерфейса Direct3DRMViewport. Если порт просмотра не отключен, сперва вызывается функция Clear() для его очистки, а затем используется функция Render() для создания нового изображения.
Хотя новое изображение уже создано функцией Render() интерфейса Direct3DRMViewport, оно пока остается невидимым. Для фактического вывода визуализированного изображения используется функция Update() интерфейса Direct3DRMDevice.
Функция AdjustField() является функцией обратного вызова, которая управляет полем зрения порта просмотра. Код этой функции выглядит так:
void ZoomWin::AdjustField(LPDIRECT3DRMFRAME, void*, D3DVALUE) { static D3DVALUE time; time += D3DVALUE(.5); animation->SetTime(time); D3DVECTOR pos; zoomframe->GetPosition(0, &pos); viewport->SetField(pos.x); }Для управления анимационной последовательностью в функции применяется статическая переменная time. Значение этой переменной увеличивается при каждом вызове функции AdjustField(). После того, как значение переменной time увеличено, осуществляется установка нового времени в анимации с помощью функции SetTime() интерфейса Direct3DRMAnimation.
Затем мы получаем координаты анимируемого фрейма (zoomframe). Вспомните, что позицией фрейма zoomframe управляет анимационная последовательность. Когда с помощью функции SetTime() мы устанавливаем новое время анимации, позиция фрейма zoomframe изменяется в соответствии с заданной анимационной последовательностью. Для получения новых координат фрейма используется функция GetPosition().
В заключение значение координаты фрейма по оси X используется в качестве нового значения угла зрения для порта просмотра. Новое значение устанавливается с помощью функции SetField() интерфейса Direct3DRMViewport. Вспомните, что viewport это статический член данных класса RMWin. Поэтому мы можем обращаться к нему из функций обратного вызова.
Сцена в приложении Zoom создается функцией CreateScene(), код которой приведен в листинге 9.1.
Листинг 9.1. Функция ZoomWin::CreateScene() |
BOOL ZoomWin::CreateScene() { // ------- СЕТКА -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_Z_MESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->SetColorRGB(D3DVALUE(.67), D3DVALUE(.82), D3DVALUE(.94)); ScaleMesh(meshbuilder, D3DVALUE(25)); // ------- МАТЕРИАЛ ---------- LPDIRECT3DRMMATERIAL material; d3drm->CreateMaterial(D3DVALUE(10), &material); meshbuilder->SetMaterial(material); material->Release(); material = 0; // ------ ФРЕЙМ СЕТКИ ------ LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->SetRotation(scene, D3DVALUE(1), D3DVALUE(.4), D3DVALUE(0), D3DVALUE(.1)); meshframe->AddVisual(meshbuilder); meshframe->Release(); meshframe = 0; // ------- АНИМАЦИЯ -------- d3drm->CreateAnimation(&animation); animation->SetOptions(D3DRMANIMATION_SPLINEPOSITION | D3DRMANIMATION_CLOSED | D3DRMANIMATION_POSITION); animation->AddPositionKey(D3DVALUE(0), D3DVALUE(.4), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(24), D3DVALUE(5), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(49), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(74), D3DVALUE(5), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(99), D3DVALUE(.4), D3DVALUE(0), D3DVALUE(0)); d3drm->CreateFrame(scene, &zoomframe); animation->SetFrame(zoomframe); // --------- СВЕТ -------- LPDIRECT3DRMLIGHT dlight; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &dlight); LPDIRECT3DRMLIGHT alight; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(.40), D3DVALUE(.40), D3DVALUE(.40), &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); camera->AddMoveCallback(AdjustField, NULL); return TRUE; } |
Функция CreateScene() выполняет следующие действия:
Создает и загружает сетку. Создает материал и применяет его к сетке. Создает фрейм для сетки. Конструирует анимационную последовательность, которая будет применяться для управления полем зрения порта просмотра. Создает два источника света. Создает порт просмотра и устанавливает функцию обратного вызова.На первом этапе мы воспользуемся интерфейсом Direct3DRMMeshBuilder чтобы загрузить сетку из файла. Давайте еще раз взглянем на код:
D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_Z_MESH); resinfo.lpType = "MESH"; meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->SetColorRGB(D3DVALUE(.67), D3DVALUE(.82), D3DVALUE(.94)); ScaleMesh(meshbuilder, D3DVALUE(25));Структура D3DRMLOADRESOURCE используется для идентификации элемента ресурсов приложения, содержащего сетку. Член данных meshbuilder инициализируется функцией CreateMeshBuilder() интерфейса Direct3DRM. Затем вызывается функция Load(), которая и загружает сетку. Цвет сетки назначается с помощью функции SetColorRGB(). После выполнения всех этих действий сетка масштабируется до размера в 25 единиц с помощью функции ScaleMesh().
Теперь к сетке применяется материал:
LPDIRECT3DRMMATERIAL material; d3drm->CreateMaterial(D3DVALUE(10), &material); meshbuilder->SetMaterial(material); material->Release(); material = 0;Материал позволяет осуществлять точную настройку внешнегго вида сетки путем изменения интенсивности и цвета отраженного света. В данном случае мы используем материал, придающий сетке вид отполированной поверхности.
СОВЕТ | Тестирование параметров материала. Xpose — это законченная программа просмотра объектов, созданная с использованием рассмотренных в этой книге технологий. И исходный код и исполняемый файл этой программы содержатся на прилагаемом к книге CD-ROM. Самый быстрый способ познакомиться с параметрами материала и тем, какое влияние они оказывают на внешний вид сетки — провести несколько экспериментов с программой Xpose. Диалоговое окно Material Settings программы Xpose позволяет изменить параметры материала и сразу же увидеть результат. |
Для создания материала используется функция CreateMaterial() интерфейса Direct3DRM. Новый материал применяется к сетке с помощью функции SetMaterial() интерфейса Direct3DRMMeshBuilder. После выполнения этих действий локальный указатель material освобождается.
На третьем этапе создается фрейм для сетки:
LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->SetRotation(scene, D3DVALUE(1), D3DVALUE(.4), D3DVALUE(0), D3DVALUE(.1)); meshframe->AddVisual(meshbuilder); meshframe->Release(); meshframe = 0;Фрейм создается функцией CreateFrame() интерфейса Direct3DRM. С помощью функции SetRotation() новому фрейму назначаются атрибуты вращения. Назначение атрибутов является единственной технологией, используемой для анимации фрейма сетки. Во время работы программы фрейм остается в начале координат (позиции по умолчанию). После того, как конструктор сеток будет присоединен к новому фрейму с помощью функции AddVisual(), указатель meshframe освобождается.
Затем конструируется анимационная последовательность, которая будет использоваться для управления полем зрения порта просмотра. Выполняющий эти действия код выглядит так:
d3drm->CreateAnimation(&animation); animation->SetOptions(D3DRMANIMATION_SPLINEPOSITION | D3DRMANIMATION_CLOSED | D3DRMANIMATION_POSITION); animation->AddPositionKey(D3DVALUE(0), D3DVALUE(.4), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(24), D3DVALUE(5), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(49), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(74), D3DVALUE(5), D3DVALUE(0), D3DVALUE(0)); animation->AddPositionKey(D3DVALUE(99), D3DVALUE(.4), D3DVALUE(0), D3DVALUE(0)); d3drm->CreateFrame(scene, &zoomframe); animation->SetFrame(zoomframe);Сначала с помощью функции CreateAnimation() интерфейса Direct3DRM создается анимация. Затем вызывается функция SetOptions() интерфейса Direct3DRMAnimation чтобы задать параметры анимации. Мы используем константу D3DRMANIMATION_SPLINEPOSITION чтобы указать, что желаем получить сплайновую анимацию (а не линейную). Этот параметр может быть изменен с помощью меню Animation приложения Zoom. Константа D3DRMANIMATION_CLOSED указывает, что анимационная последовательность должна непрерывно повторяться. Это позволяет использовать непрерывно увеличивающиеся значения времени для повторного выполнения анимационной последовательности. Константа D3DRMANIMATION_POSITION указывает, что анимационная последовательность будет изменять только местоположение объекта (мы не используем константу D3DRMANIMATION_SCALEANDROTATION поскольку, как вы вскоре увидите, мы не используем анимацию для изменения вращения или масштаба объекта).
Затем с помощью функции AddPositionKey() к анимации добавляются пять позиционных ключей. Первым аргументом функции AddPositionKey() является значение, указывающее в какой момент времени данный ключ должен вступить в действие. Оставшиеся три аргумента задают связанную с новым ключом позицию. Обратите внимание, что устанавливаемые ключи отличаются только значением координаты по оси X. Эта ось выбрана произвольно. Мы могли бы использовать оси Y или Z, поскольку мы создаем линейную, или одномерную, анимационную последовательность. Мы будем использовать в анимационной последовательности только ось X.
Затем создается фрейм с именем zoomframe. Новый фрейм присоединяется к анимационной последовательности с помощью функции SetFrame() интерфейса Direct3DRMAnimation.
Фрейм zoomframe является пустым, но он используется не так, как использовались пустые фреймы в приложениях Firefly (глава 6) или Decal (глава 5). Этот пустой фрейм не будет использоваться как родитель для других фреймов. Вместо этого он будет анимироваться созданной ранее анимационной последовательностью, а его координаты будут применяться для установки параметров поля зрения порта просмотра. Обратите внимание, что значение координаты по оси X в анимационной последовательности изменяется в диапазоне от 0.4 до 5.0. Эти значения не будут интерпретироваться как позиции. Вместо этого, они будут использоваться в качестве параметров поля зрения и передаваться в аргументах функции SetField() интерфейса Direct3DRMViewport.
Возвратимся к шести этапам работы функции CreateScene(). На пятом этапе создаются два источника света. Мы пропустим обсуждение этого этапа. Источники света были предметом обсуждения в главе 6.
На шестом, завершающем этапе, выполняется создание порта просмотра:
d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); camera->AddMoveCallback(AdjustField, NULL); return TRUE;Сначала создается и размещается фрейм camera. Затем этот фрейм передается в аргументе функции CreateViewport() интерфейса Direct3DRM. Последнее, что делает функция CreateScene() — установка функции обратного вызова AdjustField() с помощью функции AddMoveCallback(). Для установки функции обратного вызова мы используем фрейм camera, но с тем же успехом может быть использован любой другой фрейм сцены.
Большинство демонстрационных программ на CD-ROM требуют создания порта просмотра, поэтому с предметом изучения вы уже знакомы. В данной главе мы более подробно изучим порты просмотра и продемонстрируем их возможности и способы работы с ними с помощью следующих программ:
Zoom MeshPick FacePick MultiViewВ двух словах, порт просмотра это компонент, через который осуществляется просмотр сцен Direct3D. Порты просмотра — это видеокамеры, которые мы используем, чтобы увидеть наших трехмерных созданий. Чтобы воспользоваться областью просмотра, необходимо указать ее местоположение и ориентацию. Это требование выполняется, когда область просмотра присоединяется к фрейму. Местоположение и ориентация фрейма определяют, какую часть сцены мы увидим на экране.
Во всех приложениях, рассмотренных нами до этого момента, для отображения сцены использовался единственный порт просмотра. Для большинства приложений это замечательно работает, но в некоторых случаях можно использовать несколько портов просмотра, чтобы показывать одну и ту же сцену одновременно с нескольких разных точек.
Определение версии класса RMWin, используемой в приложении MultiView, приведено в листинге 9.5.
Листинг 9.5. Определение класса RMWin |
class RMWin : public CFrameWnd { public: RMWin(); RMWin(int w,int h); BOOL Create(const CString& sTitle,int icon,int menu); void SetColorModel(D3DCOLORMODEL cm ) { colormodel = cm; } inline COLORREF D3DCOLOR_2_COLORREF(D3DCOLOR d3dclr); inline D3DCOLOR COLORREF_2_D3DCOLOR(COLORREF cref); void Render(); protected: static int GetMouseX() { return mousex; } static int GetMouseY() { return mousey; } void ScaleMesh(LPDIRECT3DRMMESHBUILDER, D3DVALUE); protected: //{{AFX_MSG(RMWin) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnDestroy(); afx_msg void OnActivate(UINT state, CWnd* other, BOOL minimize); afx_msg void OnPaint(); afx_msg void OnSize(UINT type, int cx, int cy); afx_msg void OnMouseMove(UINT state, CPoint point); afx_msg BOOL OnEraseBkgnd(CDC* pDC); afx_msg void OnViewport1Disabled(); afx_msg void OnViewport1Front(); afx_msg void OnViewport1Left(); afx_msg void OnViewport1Right(); afx_msg void OnViewport1Top(); afx_msg void OnViewport2Disabled(); afx_msg void OnViewport2Front(); afx_msg void OnViewport2Left(); afx_msg void OnViewport2Right(); afx_msg void OnViewport2Top(); afx_msg void OnViewport3Disabled(); afx_msg void OnViewport3Front(); afx_msg void OnViewport3Left(); afx_msg void OnViewport3Right(); afx_msg void OnViewport3Top(); afx_msg void OnUpdateViewport1Disabled(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport1Front(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport1Left(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport1Right(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport1Top(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport2Disabled(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport2Front(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport2Left(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport2Right(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport2Top(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport3Disabled(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport3Front(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport3Left(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport3Right(CCmdUI* pCmdUI); afx_msg void OnUpdateViewport3Top(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: void Initvars(); virtual BOOL CreateScene() = 0; BOOL CreateDevice(); GUID* GetGUID(); void ConfigViewport(LPDIRECT3DRMFRAME camera, int view); void CreateViewports(); protected: static LPDIRECT3DRM d3drm; LPDIRECT3DRMFRAME scene; LPDIRECT3DRMDEVICE device; D3DCOLORMODEL colormodel; private: LPDIRECT3DRMFRAME camera1, camera2, camera3; LPDIRECT3DRMVIEWPORT viewport1, viewport2, viewport3; int view1setting, view2setting, view3setting; CRect winrect; LPDIRECTDRAWCLIPPER clipper; static int mousex; static int mousey; static UINT mousestate; friend class RMApp; }; |
Ясно видно, что это сложный класс, и мы в данном разделе не будем обсуждать все, входящие в него функции. Вместо этого мы сосредоточим внимание на тех частях класса, которые отличаются от оригинальной версии RMWin. Обсуждение остальных функций класса RMWin вы найдете в главе 4.
Были добавлены три переменные: camera1, camera2 и camera3. Это указатели на интерфейс Direct3DRMFrame, которые будут использоваться для создания и перемещения трех, созданных в приложении, портов просмотра. Тот факт, что эти переменные объявлены закрытыми, говорит нам, что классы, производные от RMWin не имеют возможности манипулировать этими указателями. Эта задача возложена исключительно на класс RMWin.
Переменные viewport1, viewport2 и viewport3 будут использованы для доступа к трем созданным в приложении портам просмотра. Эти переменные также закрытые, поэтому для их инициализации могут использоваться только функции класса RMWin.
Кроме того, добавлены еще три переменные: view1setting, view2setting и view3setting. Эти значения используются для того, чтобы указать, как должен быть расположен каждый из портов просмотра. Переменные используются совместно со следующими константами (определенными в файле resource.h):
VIEWPORT_DISABLED VIEWPORT_FRONT VIEWPORT_LEFT VIEWPORT_RIGHT VIEWPORT_TOPБыли добавлены две закрытые функции: ConfigViewport() и CreateViewports(). Функция ConfigViewport() используется для назначения новой ориентации порта просмотра с учетом его текущей конфигурации. Функция CreateViewports() инициализирует три используемых в приложении порта просмотра.
Остальные добавленные функции являются обработчиками сообщений команд меню, добавленными с помощью ClassWizard. Как вы увидите позднее, эти функции используются для изменения местоположения и ориентации портов просмотра.
В классе FacePickWin сосредоточена основная функциональность приложения FacePick:
class FacePickWin : public RMWin { public: FacePickWin(); BOOL CreateScene(); protected: //{{AFX_MSG(FacePickWin) afx_msg void OnRenderWireframe(); afx_msg void OnRenderGouraud(); afx_msg void OnRenderFlat(); afx_msg void OnUpdateRenderWireframe(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderFlat(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGouraud(CCmdUI* pCmdUI); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg void OnLButtonUp(UINT nFlags, CPoint point); afx_msg void OnColorsFace(); afx_msg void OnColorsMesh(); afx_msg void OnFileOpen(); afx_msg void OnFileSave(); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: static void UpdateDrag(LPDIRECT3DRMFRAME, void*, D3DVALUE); int PickFace(const CPoint& point); void OnIdle(long); private: LPDIRECT3DRMMESHBUILDER meshbuilder; LPDIRECT3DRMFRAME meshframe; D3DCOLOR pickcolor; D3DVALUE meshscale; static BOOL drag; static BOOL end_drag; static int last_x, last_y; };В классе объявлены две открытые функции: конструктор и функция CreateScene(). Конструктор используется для инициализации динамических переменных класса. Функция CreateScene() создает начальную сцену приложения.
Далее объявлены двенадцать защищенных функций. Первые шесть из них предназначены для обработки сообщений меню Render, присутствующего в большинстве демонстрационных приложений. Функции OnLButtonDown() и OnLButtonUp() используются для реакции приложения на изменение состояния кнопок мыши.
Функции OnColorsFace() и OnColorsMesh() реализуют функциональность меню Colors. Обе функции выводят диалоговое окно выбора цвета с помощью класса MFC CColorDialog.
Функции OnFileOpen() и OnFileSave() используют класс MFC CFileDialog чтобы позволить пользователю выбрать или ввести имя файла.
Кроме того объявлены три закрытые функции: UpdateDrag(), PickFace() и OnIdle(). Функция UpdateDrag() — это функция обратного вызова, используемая для изменения ориентации сетки. Функция PickFace() подобна функции PickMesh(), которую мы использовали в приложении PickMesh. PickFace() отвечает за выполнение операции выбора граней.
Функциональность приложения MeshPick предоставляется классом MeshPickWin:
class MeshPickWin : public RMWin { public: MeshPickWin(); BOOL CreateScene(); protected: //{{AFX_MSG(MeshPickWin) afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg void OnLButtonUp(UINT nFlags, CPoint point); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: static void UpdateDrag(LPDIRECT3DRMFRAME frame, void*, D3DVALUE); BOOL PickMesh(const CPoint& point); private: static DragData drag; };В классе объявлены две открытые функции: конструктор и функция CreateScene(). Конструктор инициализирует единственный член данных класса, а функция CreateScene() создает сцену для приложения.
Кроме того, в классе объявлены два защищенных обработчика сообщений: OnLButtonDown() и OnLButtonUp(). MFC вызывает эти функции чтобы уведомить приложение об изменении состояния левой кнопки мыши. Мы будем использовать функцию OnLButtonDown() для выполнения процедуры выбора объекта и для инициализации операции перетаскивания, позволяющей с помощью мыши перемещать выбранный объект. Функция OnLButtonUp() используется для выключения режима перетаскивания.
Затем размещено объявление двух закрытых функций: UpdateDrag() и PickMesh(). UpdateDrag()— это функция обратного вызова, используемая для опроса текущего состояния приложения. Если включен режим перетаскивания, функция UpdateDrag() изменяет местоположение выбранной в данный момент сетки, основываясть на положении указателя мыши. Функция PickMesh() используется для выполнения самой операции выбора объекта.
В классе объявлен только один член даных — структура DragData. Ее объявление выглядит следующим образом:
struct DragData { LPDIRECT3DRMFRAME frame; POINT mousedown; D3DVALUE origx,origy; };Структура содержит данные, имеющие отношение к выполняемой операции перетаскивания. Член структуры frame указывает на фрейм, к которому присоединена перетаскиваемая сетка. Поле mousedown используется для сохранения координат указателя мыши в момент инициализации операции перетаскивания. Члены данных origx и origy применяются для сохранения координат фрейма в момент начала перетаскивания.
Основная (но не вся) функциональность приложения MultiView предоставляется классом MultiViewWin:
class MultiViewWin : public RMWin { public: MultiViewWin(); BOOL CreateScene(); protected: //{{AFX_MSG(MultiViewWin) 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 void OnFileOpen(); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: LPDIRECT3DRMMESHBUILDER meshbuilder; LPDIRECT3DRMFRAME meshframe; };В классе объявлены две открытые функции: конструктор и функция CreateScene(). Конструктор инициализирует две переменные класса. Функция CreateScene() создает сцену приложения, но отличается от подобных функций в других демонстрационных программах, поскольку не создает никаких портов просмотра. Код функции CreateScene() мы рассмотрим в следующем разделе.
Далее объявлены семь защищенных функций. Шесть из них являются обработчиками сообщений меню Render и присутствуют в большинстве других приложений. Седьмая функция является обработчиком сообщений для команды Open меню File. Мы будем использовать ее для отображения диалогового окна выбора файлов и загрузки выбранной сетки с диска.
Две переменных класса являются указателями, используемыми для доступа к единственной сетке приложения и фрейму, к которому эта сетка присоединена.
Функциональные возможности приложения Zoom сосредоточены в классе ZoomWin:
class ZoomWin : public RMWin { public: ZoomWin(); BOOL CreateScene(); protected: //{{AFX_MSG(ZoomWin) 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 void OnAnimationLinear(); afx_msg void OnAnimationSpline(); afx_msg void OnUpdateAnimationLinear(CCmdUI* pCmdUI); afx_msg void OnUpdateAnimationSpline(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: static void AdjustField(LPDIRECT3DRMFRAME frame, void*, D3DVALUE); private: LPDIRECT3DRMMESHBUILDER meshbuilder; static LPDIRECT3DRMFRAME zoomframe; static LPDIRECT3DRMANIMATION animation; };В классе объявлены две открытые функции: конструктор и функция CreateScene(). Конструктор используется для инициализации динамических членов данных класса. (Статические члены данных класса получают нулевые значения автоматически, поэтому нет никакой необходимости заниматься их инициализацией.) Поскольку в классе есть только один динамический член данных, код выглядит следующим образом:
ZoomWin::ZoomWin() { meshbuilder = 0; }Функция CreateScene() создает сцену приложения. Мы рассмотрим ее код чуть позже.
Далее следует объявление десяти защищенных функций. Первые шесть из них являются обработчиками сообщений, необходимыми для реализации меню Render. Оставшиеся четыре функции необходимы для поддержки меню Animation. Функции, относящиеся к меню Animation мы рассмотрим чуть позже. Функции, реализующие меню Render аналогичны подобным функциям из рассмотренных ранее приложений.
Потом расположено объявление функции обратного вызова AdjustField(). Эта функция используется для изменения параметров поля зрения порта просмотра во время работы программы.
В самом конце объявлены три закрытых члена данных:
LPDIRECT3DRMMESHBUILDER meshbuilder; static LPDIRECT3DRMFRAME zoomframe; static LPDIRECT3DRMANIMATION animation;Указатель meshbuilder используется для доступа к сетке демонстрационной программы. Он применяется в функции CreateScene() и в шести защищенных обработчиках сообщений меню Render. Переменная zoomframe является указателем на пустой фрейм, а переменная animation— это указатель на интерфейс Direct3DRMAnimation. Мы будем использовать интерфейс анимации и пустой фрейм при «анимации» поля зрения порта просмотра. О том, как это работает, мы поговорим во время обсуждения функции AdjustField().
Приложение MultiView написано слегка иначе, чем остальные демонстрационные программы на CD-ROM. Все примеры для своего класса приложения в качестве базового класса используют класс RMWin. Большинство демонстрационных программ использует одну и ту же версию RMWin, но в приложении MultiView применяется модифицированная версия.
Версия класса RMWin, которая используется в приложении MultiView включает поддержку трех портов просмотра. Это потребовало внесения изменений в ряд функций класса RMWin. Таким образом, при обсуждении кода приложения MultiView мы будем рассматривать как функции класса RMWin так и функции класса MultiViewWin.
Во всех демонстрационных программах, которые мы рассмотрели в предыдущих главах, мы не задавали параметры поля зрения или угол обзора камеры. Это означает, что мы использовали устанавливаемое по умолчанию значение поля зрения, равное0.5.
Для изменения поля зрения порта просмотра используется функция SetField() интерфейса Direct3DRMViewport. Меньшие значения уменьшают поле зрения порта просмотра и оказывают эффект, сравнимый с использованием телеобъектива. Большие значения увеличивают поле зрения порта просмотра, подобно широкоугольному объективу.
Приложение FacePick отображает на экране сетку и позволяет выбирать отдельные ее грани. Выбранная грани меняет свой цвет на тот, который указан в меню Colors. Сетку можно поворачивать, выбрав любую часть порта просмотра, которая не занята сеткой и перемещая мышь. Это позволяет менять ориентацию сетки и дает возможность менять цвет любой грани сетки. Кроме того, меню File позволяет загружать и сохранять сетки. Внешний вид окна приложения FacePick показан на рис.9.3.
Рис. 9.3. Приложение FacePick
Приложение FacePick демонстирирует использование следующих технологий:
Использование функции Pick() интерфейса Direct3DRMViewport для операции выбора граней. Использование мыши для манипуляций с видимыми объектами. Загрузка и сохранение файлов сеток. Использование класса MFC CFileDialog. Использование класса MFC CColorDialog.Приложение MeshPick отображает девять сферических сеток и позволяет использовать мышь для их выбора и перетаскивания. Сетки могут быть размещены одна поверх другой, что позволяет проверить точность работы механизма выбора объектов. Внешний вид окна приложения MeshPick показан на рис. 9.2.
Рис. 9.2. Приложение MeshPick
Приложение MeshPick демонстрирует использование следующих техник:
Использование функции Pick() интерфейса Direct3DRMViewport. Использование мыши для манипуляций с видимыми объектами сцены.Мы подробно обсудим каждую из этих техник при изучении кода приложения MeshPick.
Приложение MultiView отображает единственную вращающуюся сетку, но для ее показа используются три порта просмотра. Приложение предоставляет меню, позволяющее настроить или отключить любой из портов просмотра. Кроме того, приложение позволяет загружать другие сетки. Вид окна приложения MultiView показан на рис.9.4.
Рис. 9.4. Приложение MultiView
Приложение MultiView демонстрирует использование следующих технологий:
Использование в одном приложении нескольких портов просмотра. Использование команд меню для настройки местоположения порта просмотра. Использование класса MFC CFileDialog для реализации команды Open меню File.В фотографии линза, используемая для изменения поля зрения, называется трансфокатором. Трансфокаторы позволяют получить крупный план удаленных объектов и общий план для просмотра больших пейзажей.
В приложении Zoom мы воспользуемся функцией SetField() для настройки поля зрения порта просмотра. Приложение размещает сетку в начале координат, а потом демонстрирует результаты трансфокации, используя размещенную в фиксированной позиции камеру. Внешний вид окна приложения Zoom показан на рис. 9.1 (учтите, что на рисунке вы не увидите результаты изменения угла зрения).
Рис. 9.1. Приложение Zoom
Если вы запустите приложение Zoom, вам покажется, что сетка перемещается. Но, хотя сетка и вращается, ее местоположение остается неизменным. Иллюзия перемещения вызывается изменениями параметров поля зрения порта просмотра.
Приложение Zoom поддерживает обычное меню Render, позволяющее во время работы программы изменять используемый метод визуализации. Кроме того, в приложении есть меню Animation. Оно позволяет изменять используемый способ анимации (линейную или сплайновую).
Приложение Zoom демонстрирует следующие технологии:
Использование функции SetField() интерфейса Direct3DRMViewport. Использование интерфейса Direct3DRMAnimation для выполнения универсальной анимации (не анимации фреймов). Изменение метода визуализации сетки во время работы приложения. Использование интерфейса Direct3DRMMaterial для изменения внешнего вида сетки.Теперь мы знаем, как можно использовать порт просмотра для выбора сеток. Но что делать, если мы хотим выбирать отдельные грани сетки? Никаких проблем. Фактически, необходимый для этого код будет очень похож на код, который использовался при выборе сеток.
Порты просмотра Direct3D предоставляют поддержку выбора объектов (picking). Выбор объекта— это указание интересующего объекта путем указания его местоположения в порте просмотра. Обычно для указания местоположения объектов используется мышь. Порт просмотра использует местоположение указателя мыши, чтобы определить, какой из объектов был выбран. Выбор объектов полезен в приложениях, которые требуют точного и инитуитивно понятного выделения объектов.
Выбор объектов сильно влияет на быстродействие. Он требует, чтобы Direct3D выполнил сортировку внутренних структур и данных буферизации. Это нетривиальная задача. Фактически, часто в момент выполнения операции выбора объекта наблюдается видимая задержка анимации.
С другой стороны, выбор объектов работает с точностью до пикселя. Direct3D может точно определить, какой объект был выбран, основываясь на местоположении указателя мыши. С помощью операции выбора объектов могут быть выбраны только видимые объекты.