netlib.narod.ru | < Назад | Оглавление | Далее > |
Термин «трансформация» часто ассоциируется с телевизионной рекламой или спецэффектами в фильмах, когда один объект плавно превращается в другой. Обычно начальная и конечная форма объекта весьма отличаются. В данном разделе мы обсудим общие принципы трансформации.
Для наших целей не потребуется сосредотачиваться на том, насколько отличаются две формы объекта, достаточно только чтобы одна форма могла быть преобразована в другую (и наоборот). Чтобы два объекта (сетки) могли быть преобразованы один в другой, они должны содержать одинаковое количество вершин. Выполенеие этого условия не гарантирует, что полученная в результате последовательность трансформаций будет хорошо выглядеть. Одинаковое число вершин означает только возможность существования такой последовательности.
С практической точки зрения, количество вершин это только половина вопроса. Грани в двух сетках должны быть упорядочены одинаковым образом. Фактически, в большинстве случаев трансформации используется несколько вариантов одной и той же сетки.
Сетка, являющаяся частью набора трансформируемых сеток, называется шагом трансформации (morph target). В нашем обсуждении мы не будем касаться вопросов разработки и создания шагов трансформации. Мы сосредоточимся на том, как выполнить трансформацию, включающую два или более совместимых шага.
Формально трансформация означает вычисление позиций вершин сетки, основываясь на следующих трех критериях:
Предположим, что сетка начального шага трансформации представляет собой птицу, сетка конечного шага трансформации — самолет, а число промежуточных изменений равно 100. Если мы используем значение 1, то полученная в результате сетка будет выглядеть как птица. Если мы используем значение 100, — сетка будет выглядеть как самолет. Если же мы используем значение 50, то полученная в результате сетка будет выглядеть как нечто среднее между птицей и самолетом. Чем ближе используемое значение к 1, тем больше сетка похожа на птицу. Аналогично, чем ближе значение к 100, тем больше сетка похожа на самолет.
Шаги трансформации не обязаны полностью отличаться друг от друга. Последовательность трансформаций может включать сгибание, искривление, скручивание, сжатие или вытягивание объекта.
Также нет никаких причин, по которым количество шагов трансформации должно ограничиваться двумя. Можно использовать любое количество шагов. Например, птица может сначала трансформироваться в шар, а затем шар трансформируется в самолет. Для этого достаточно добавить сферу в качестве шага трансформации расположенного между шагами птицы и самолета. Последовательность трансформаций может быть создана, если каждый шаг трансформации имеет одинаковое количество вершин.
Приложение MorphPlay является универсальным проигрывателем последовательностей трансформаций. Оно позволяет загрузить и воспроизвести файл трансформации (.MRF). На идущем вместе с книгой CD-ROM вы можете найти несколько файлов MRF в каталоге MESHES.
Файлы MRF — это X-файлы, отвечающие следующим требованиям:
Расширение MRF является необязательным. Приложение MorphPlay может загружать как файлы с расширением MRF, так и файлы с расширением X. После загрузки файла, содержащиеся в нем сетки становятся шагами трансформации. Файл может содержать до 26 шагов трансформации — по числу букв алфавита. Шаги трансформации используются в алфавитном порядке.
В приложении есть меню Morph, состоящее из трех пунктов: Forward, Reverse и Both. При выборе пункта Forward процесс трансформации начинается с первой сетки (сетки a), которая преобразуется во вторую сетку (сетку b) и т.д. Процесс трансформации продолжается пока не будет достигнута последняя из содержащихся в файле сеток. Затем воспроизведение начинается заново с самой первой сетки. При выборе пункта Reverse трансформация идет в обратном направлении — от последней сетки к первой. Выбранный по умолчанию пункт, Both, вызывает попеременную трансформацию в прямом и обратном направлениях.
Меню Speed позволяет изменить скорость трансформации. В приложении можно выбирать одну из пяти возможных скоростей. Меньшее значение скорости означает более медленное приращение времени, и в результате получается более плавное движение.
С помощью мыши можно поворачивать и наклонять трансформируемую сетку. Нажмите и удерживайте левую кнопку мыши, после чего перемещайте мышь — это позволит вам рассмотреть процесс трансформации с различных углов.
Внешний вид окна приложения MorphPlay показан на рис. 8.4.
Рис. 8.4. Приложение MorphPlay
Приложение MorphPlay демонстрирует использование следующих технологий:
В отличие от большинства демонстрационных программ на CD-ROM, функциональность приложения MorphPlay разделена между двумя классами. Функции, необходимые для выполнения трансформации сосредоточены в классе MorphWin, а вся специфическая для данного приложения функциональность находится в классе MorphPlayWin. Иерархия классов приложения показана на рис. 8.5.
Рис. 8.5. Иерархия классов приложения MorphPlay
Изоляция функций трансформации в классе MorphWin позволяет упростить создание ваших собственных приложений, использующих трансформацию. Наследование класса окна от класса MorphWin (а не от класса RMWin) означает, что класс окна будет наследовать возможности трансформации.
Функциональность класса MorphWin предоставляется следующими функциями:
Функция LoadMorphSequence() получает имя MRF или X-файла и создает последовательность трансформаций, основываясь на содержимом файла. Функция GetNumMorphTargets() возвращает число шагов трансформации в текущей последовательности трансформаций. Функция GetMorphMesh() возвращает указатель на интерфейс Direct3DRMMesh, представляющий трансформируемую сетку.
Функции AddMorphKey(), DeleteMorphKey() и SetMorphTime() вдохновлены интерфейсом Direct3DRMAnimation. Ключ трансформации (morph key) — это то же самое, что и шаг трансформации. Функция AddMorphKey() позволяет указать какой шаг трансформации должен вступить в действие в заданный момент последовательности трансформаций. Функция SetMorphTime() позволяет указать момент последовательности трансформаций, который должен быть вычислен.
Объявление класса MorphWin выглядит следующим образом:
class MorphWin : public RMWin { public: MorphWin(); LPDIRECT3DRMMESH GetMorphMesh() { return morphmesh; } DWORD GetNumMorphTargets() { return nummorphtargets; } BOOL LoadMorphSequence(const CString& filename); BOOL AddMorphKey(DWORD target, D3DVALUE time); BOOL DeleteMorphKey(D3DVALUE time); BOOL SetMorphTime(D3DVALUE time); private: BOOL LoadMeshes(const CString& filename); BOOL CreateAnimations(); BOOL PrepareMorphVertices(); BOOL ReleaseAnimations(int count); protected: //{{AFX_MSG(MorphWin) afx_msg void OnDestroy(); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: LPDIRECT3DRMMESH morphmesh; D3DRMVERTEX* morphmeshdata[MAXMORPHTARGETS]; D3DRMVERTEX* morphvertex; DWORD nummorphvertices; DWORD nummorphtargets; LPDIRECT3DRMANIMATION* posanimation; LPDIRECT3DRMFRAME* posframe; LPDIRECT3DRMANIMATION* normanimation; LPDIRECT3DRMFRAME* normframe; BOOL morphing; };
Шесть функций, которые мы обсудили, и конструктор класса объявлены открытыми. Кроме того, в классе объявлены четыре вспомогательные закрытые функции. Для освобождения ресурсов в классе объявлен обработчик сообщения OnDestroy().
Массив morphmeshdata используется для хранения данных вершин каждого входящего в последовательность шага трансформации. Массив morphvertex используется для хранения данных вершин трансформируемой сетки.
Переменная nummorphvertices хранит число вершин в шаге трансформации. Нам не требуются отдельные переменные для хранения числа вершин в каждом из шагов трансформации, поскольку во всех шагах должно быть одно и то же количество вершин. Переменная nummorphtargets используется для хранения числа шагов трансформации в последовательности.
Массивы posanimation и posframe используются для вычисления новых позиций вершин. Массив posanimation — это массив указателей на интерфейс Direct3DRMAnimation. Позиции вершин определяются путем генерации анимационной последовательности для каждой вершины трансформируемой сетки.
Массив posframe является массивом фреймов, которые будут использоваться исключительно для получения данных анимации из объекта Direct3DRMAnimation.
Массивы normanimation и normframe используются для вычисления нормалей для каждой из вершин. Это необходимо, чтобы во время воспроизведения последовательности трансформации правильно рассчитывалось освещение граней. Нормали вершин трансформируются совместно с позициями вершин с использованием той же самой технологии.
И, наконец, логическая переменная morphing показывает загружена ли последовательность трансформаций.
Функция LoadMorphSequence() получает в качестве аргумента имя файла и пытается создать на основе содержимого файла последовательность трансформаций:
BOOL MorphWin::LoadMorphSequence( const CString& filename ) { CString windowtext; GetWindowText(windowtext); CString txt = "Loading: " + filename; SetWindowText(txt); BOOL ret = FALSE; if (LoadMeshes(filename)) if (CreateAnimations()) ret = PrepareMorphVertices(); SetWindowText(windowtext); return ret; }
Сначала функция получает текст, который в данный момент отображается в заголовке окна. Для этого используется функция MFC GetWindowText(), а полученный текст сохраняется в объекте windowtext. Затем текст в заголовке окна заменяется на строку с имененм загружаемого файла. Для отображения этой строки используется функция SetWindowText(). Обратите внимание, что перед возвратом из функции LoadMorphSequence() функция SetWindowText() вызывается еще раз — теперь для восстановления оригинального текста заголовка окна.
Затем в функции LoadMorphSequence() расположены три взаимосвязанных вызова функций. Функция LoadMeshes() применяется для извлечения сеток из файла. Функция CreateAnimation() подготавливает анимационные последовательности для каждой из вершин. Функция PrepareMorphVertices() используется для инициализации массива, который будет хранить вычисленные данные вершин. Если любая из этих функций завершится неудачно, функция LoadMorphSequence() возвращает FALSE.
Функция LoadMeshes() отвечает за загрузку сеток из указанного файла. Она должна проверить, что каждая сетка содержит одно и тоже количество вершин, а также извлечь и сохранить данные вершин. По этой причине код функции получился весьма длинным (и уродливым). Вы можете взглянуть на него в листинге 8.3.
Листинг 8.3. Функция MorphWin::LoadMeshes() |
BOOL MorphWin::LoadMeshes(const CString& filename) { for (DWORD i = 0; i < nummorphtargets; i++) delete [] morphmeshdata[i]; nummorphtargets = 0; if (morphmesh) { morphmesh->Release(); morphmesh = 0; } BOOL load_ok = TRUE; for (i = 0; i <MAXMORPHTARGETS && load_ok; i++) { CString msg; HRESULT r; LPDIRECT3DRMMESHBUILDER builder; d3drm->CreateMeshBuilder(&builder); r = builder->Load((void*)(LPCTSTR)filename, (void*)morphmeshname[i], D3DRMLOAD_FROMFILE | D3DRMLOAD_BYNAME, NULL, NULL); load_ok = r == D3DRM_OK; if (r == D3DRMERR_FILENOTFOUND) { TRACE("file not found\n"); CString msg = filename + ": file not found"; AfxMessageBox(msg); morphing = FALSE; } if (!load_ok) goto nomoremeshes; D3DVALUE scale; if (i == 0) scale = ScaleMesh(builder, D3DVALUE(25)); else builder->Scale(scale, scale, scale); builder->SetQuality(D3DRMRENDER_FLAT); LPDIRECT3DRMMESH mesh; builder->CreateMesh(&mesh); unsigned vcount, fcount; DWORD ds; mesh->GetGroup(0, &vcount, &fcount, 0, &ds, 0); if (i == 0) nummorphvertices = vcount; else if (vcount != nummorphvertices && load_ok) { TRACE("invalid vertex count\n"); AfxMessageBox("Invalid vertex count"); morphing = FALSE; return FALSE; } morphmeshdata[i] = new D3DRMVERTEX[nummorphvertices]; r = mesh->GetVertices(0, 0, nummorphvertices, morphmeshdata[i]); if (r != D3DRM_OK) TRACE("mesh->GetVertices() failed\n"); msg.Format("Mesh %d - %d vertices, %d faces", i+1, vcount, fcount); SetWindowText(msg); if (i == 0) morphmesh = mesh; else { mesh->Release(); mesh = 0; } nomoremeshes: builder->Release(); builder = 0; } nummorphtargets = i - 1; morphing = TRUE; return TRUE; } |
Данная функция вызывается при загрузке новой последовательности трансформаций, а значит в момент обращения к функции LoadMeshes() уже может существовать загруженная ранее последовательность. Поэтому первое, что должна сделать функция, — освободить любые выделенные ранее ресурсы.
Затем функция в цикле пытается загрузить 26 шагов трансформации. Цикл прекращается если очередная сетка не будет найдена, либо если обнаруженная сетка содержит другое количество вершин. Когда сетка загружена, функция GetVertices() интерфейса Direct3DRMMesh извлекает данные вершин сетки. Кроме того, в заголовке окна отображается новое сообщение. Функция также инициализирует переменные класса nummorphvertices и nummorphtargets.
Функция CreateAnimations() вызывается только после успешного завершения работы функции LoadMeshes(). Она отвечает за инициализацию объектов Direct3DRMAnimation, которые будут выполнять вычисление местоположения вершин и нормалей вершин при трансформации. Код этой функции выглядит так:
BOOL MorphWin::CreateAnimations() { static int vertexcount; ReleaseAnimations(vertexcount); vertexcount = nummorphvertices; posanimation = new LPDIRECT3DRMANIMATION[nummorphvertices]; posframe = new LPDIRECT3DRMFRAME[nummorphvertices]; normanimation = new LPDIRECT3DRMANIMATION[nummorphvertices]; normframe = new LPDIRECT3DRMFRAME[nummorphvertices]; for (DWORD vert = 0; vert < nummorphvertices; vert++) { d3drm->CreateAnimation(&posanimation[vert]); posanimation[vert]->SetOptions(D3DRMANIMATION_LINEARPOSITION | D3DRMANIMATION_POSITION); d3drm->CreateFrame(scene, &posframe[vert]); posanimation[vert]->SetFrame(posframe[vert]); d3drm->CreateAnimation(&normanimation[vert]); normanimation[vert]->SetOptions(D3DRMANIMATION_LINEARPOSITION | D3DRMANIMATION_POSITION); d3drm->CreateFrame(scene, &normframe[vert]); normanimation[vert]->SetFrame(normframe[vert]); } return TRUE; }
Сначала с помощью функции ReleaseAnimations() выполняется освобождение любых выделенных ранее ресурсов. Количество вершин сохраняется в статической переменной vertexcount для последующих вызовов функции ReleaseAnimations().
Затем инициализируются массивы posanimation, posframe, normanimation и normframe. В цикле создается по два экземпляра объекта Direct3DRMAnimation для каждой вершины. Объекты анимации в массиве posanimation будут использованы для вычисления местоположения вершин, а объекты анимации из массива normanimation применяются для вычисления нормалей вершин.
Функция PrepareMorphVertices() выделяет память для массива, который будет использоваться для хранения вычисленных данных вершин:
BOOL MorphWin::PrepareMorphVertices() { if (morphvertex) { delete [] morphvertex; morphvertex = 0; } morphvertex = new D3DRMVERTEX[nummorphvertices]; return TRUE; }
Как и в двух предыдущих функциях, в случае обнаружения ранее выделенных ресурсов сначала выполняется их освобождение, после чего выделяются новые ресурсы.
Функция GetNumMorphTargets() определена в объявлении класса MorphWin:
DWORD GetNumMorphTargets() { return nummorphtargets; }
Функция просто возвращает значение, хранящееся в переменной класса nummorphtargets.
Функция AddMorphKey() связывает шаг трансформации с заданным моментом времени анимации. В качестве аргументов функция получает индекс шага и временную метку.
BOOL MorphWin::AddMorphKey(DWORD target, D3DVALUE time) { if (target < 0 || target > nummorphtargets) return FALSE; for (DWORD i = 0; i < nummorphvertices; i++) { D3DVECTOR& pos = morphmeshdata[target][i].position; posanimation[i]->AddPositionKey(time, pos.x, pos.y, pos.z); D3DVECTOR& norm = morphmeshdata[target][i].normal; normanimation[i]->AddPositionKey(time, norm.x, norm.y, norm.z); } return TRUE; }
Сначала выполняется проверка допустимости индекса шага трансформации. Если индекс выходит за допустимые пределы, функция возвращает FALSE. В ином случае полученный индекс шага трансформации используется для добавления позиционных ключей в объект анимации местоположения и объект анимации нормали каждой из вершин. Для добавления ключей применяется функция AddPositionKey() интерфейса Direct3DRMAnimation. Обратите внимание, что в качестве первого аргумента функции AddPositionKey() используется параметр time.
Функция SetMorphTime() отвечает за генерацию и установку местоположения вершин и нормалей. Код функции выглядит следующим образом:
BOOL MorphWin::SetMorphTime(D3DVALUE time) { for (DWORD v = 0; v < nummorphvertices; v++) { posanimation[v]->SetTime(time); D3DVECTOR pos; posframe[v]->GetPosition(scene, &pos); morphvertex[v].position = pos; normanimation[v]->SetTime(time); D3DVECTOR norm; normframe[v]->GetPosition(scene, &norm); morphvertex[v].normal = norm; } morphmesh->SetVertices(0, 0, nummorphvertices, morphvertex); return TRUE; }
Для перебора всех вершин используется цикл. Функция SetTime() интерфейса Direct3DRMAnimation применяется для обновления анимационных последовательностей для местоположения вершин и для нормалей вершин. Затем фрейм, присоединенный к каждому объекту анимации используется для присваивания значений элементам массива morphvertex.
Как только для каждой вершины будут сгенерированы новые значения координат и нормалей, вычисленные данные присваиваются сетке с помощью функции SetVertices() интерфейса Direct3DRMMesh.
Функция GetMorphMesh() просто возвращает указатель на трансформируемую сетку. Она определена в объявлении класса следующим образом:
LPDIRECT3DRMMESH GetMorphMesh() { return morphmesh; }
Данная функция позволяет классам, наследуемым от MorphWin отображать сетку и изменять ее параметры. Как это делается мы увидим ниже.
Класс MorphPlayWin построен на базе класса MorphWin для создания законченного приложения. Определение класса выглядит следующим образом:
class MorphPlayWin : public MorphWin { public: MorphPlayWin(); BOOL CreateScene(); protected: //{{AFX_MSG(MorphPlayWin) afx_msg void OnFileOpen(); afx_msg void OnMorphForward(); afx_msg void OnMorphReverse(); afx_msg void OnMorphBoth(); afx_msg void OnUpdateMorphForward(CCmdUI* pCmdUI); afx_msg void OnUpdateMorphReverse(CCmdUI* pCmdUI); afx_msg void OnUpdateMorphBoth(CCmdUI* pCmdUI); afx_msg void OnSpeedExtrafast(); afx_msg void OnSpeedFast(); afx_msg void OnSpeedMedium(); afx_msg void OnSpeedSlow(); afx_msg void OnSpeedExtraslow(); afx_msg void OnUpdateSpeedExtrafast(CCmdUI* pCmdUI); afx_msg void OnUpdateSpeedFast(CCmdUI* pCmdUI); afx_msg void OnUpdateSpeedMedium(CCmdUI* pCmdUI); afx_msg void OnUpdateSpeedSlow(CCmdUI* pCmdUI); afx_msg void OnUpdateSpeedExtraslow(CCmdUI* pCmdUI); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg void OnLButtonUp(UINT nFlags, CPoint point); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: BOOL InitMorphSequence(const CString&); static void UpdateMorph(LPDIRECT3DRMFRAME frame, void*, D3DVALUE); static void UpdateDrag(LPDIRECT3DRMFRAME frame, void*, D3DVALUE); void OnIdle(LONG); private: LPDIRECT3DRMFRAME frame; LPDIRECT3DRMMESH mesh; int morphspeed; D3DVALUE morphtimeinc; D3DVALUE maxmorphtime; static D3DVALUE morphtime; static BOOL drag; static BOOL end_drag; static int last_x, last_y; };
В классе объявлены две открытые функции: конструктор и функция CreateScene(). Конструктор выполняет инициализацию членов данных класса. Функция CreateScene() инициализирует источники света и порт просмотра приложения.
Для обработки команды Open меню File класс предоставляет функцию OnFileOpen(). Для представления диалогового окна выбора файлов используется функция класса MFC CFileDialog.
Для поддержки меню Morph предназначены три обработчика сообщений: OnMorphForward(), OnMorphReverse() и OnMorphBoth(). Следующие пять обработчиков предназначены для реализации команд меню Speed: OnSpeedExtrafast(), OnSpeedFast(), OnSpeedMedium(), OnSpeedSlow() и OnSpeedExtraslow(). Каждой из этих функций соответствует вспомогательная функция OnUpdate...(). Функции OnLButtonDown() и OnLButtonUp() используются для начала и прекращения операций перетаскивания объекта мышью (для вращения сетки).
Далее расположено объявление функции InitMorphSequence(). Она применяется для загрузки новой последовательности трансформаций. Функция обратного вызова UpdateMorph() контролирует скорость и направление воспроизведения последовательности трансформаций. Функция обратного вызова UpdateDrag() используется для реализации операций перетаскивания объекта мышью, когда сетка вращается в соответствии с перемещениями мыши. Функция OnIdle() останавливает вращение сетки на время перетаскивания.
Далее расположены объявления членов данных класса. Переменные frame и mesh являются соответственно указателем на фрейм трансформируемой сетки и указателем на трансформируемую сетку. Переменные morphspeed, morphtimeinc, maxmorphtime и morphtime применяются для управления последовательностью трансформаций. Переменные drag, end_drag, last_x и last_y используются при вращении фрейма сетки во время операции перетаскивания.
Функция MorphPlayWin::CreateScene() выглядит следующим образом:
BOOL MorphPlayWin::CreateScene() { // --------НАПРАВЛЕННЫЙ И РАССЕЯННЫЙ СВЕТ-------- 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)); alight->Release(); alight = 0; dlight->Release(); dlight = 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(), рассмотренных нами в этой книге, версия из класса MorphPlayWin выполняет только два действия. На первом этапе выполняется создание двух источников света. На втором этапе создается порт просмотра.
Поскольку к сцене не добавляется никаких видимых объектов, сразу после запуска приложение отображает пустое окно. Чтобы загрузить последовательность трансформаций, следует воспользоваться командой Open меню File.
Выбор команды Open меню File приводит к тому, что MFC вызывает функцию OnFileOpen(). Код этой функции показан ниже:
void MorphPlayWin::OnFileOpen() { static char BASED_CODE filter[] = "Morph Files (*.mrf)|*.mrf|X Files (*.x)|*.x||"; CFileDialog opendialog(TRUE, 0, 0, OFN_FILEMUSTEXIST, filter, this); if (opendialog.DoModal() == IDOK) { CString filename = opendialog.GetPathName(); CWaitCursor cur; InitMorphSequence(filename); } }
Функция использует класс MFC CFileDialog для представления диалогового окна выбора файлов в котором будут отображаться только файлы с расширениями MRF и X. Если пользователь выбирает файл, или вводит его имя, выполняется вызов функции InitMorphSequence() и имя выбранного файла передается ей как аргумент.
Функция InitMorphSequence() использует унаследованные от класса MorphWin функции для начала новой последовательности трансформаций:
BOOL MorphPlayWin::InitMorphSequence(const CString& filename) { if (frame) frame->DeleteVisual(mesh); else { d3drm->CreateFrame(scene, &frame); frame->AddMoveCallback(UpdateDrag, NULL); frame->AddMoveCallback(UpdateMorph, this); } if (LoadMorphSequence(filename) == FALSE) return FALSE; DWORD targets = GetNumMorphTargets(); for (DWORD i = 0; i < targets; i++) AddMorphKey(i, D3DVALUE(i)); maxmorphtime = D3DVALUE(targets - 1); morphtime = D3DVALUE(0.0); mesh = GetMorphMesh(); mesh->SetGroupColorRGB(0, D3DVALUE(.67), D3DVALUE(.82), D3DVALUE(.94)); frame->AddVisual(mesh); return TRUE; }
Сначала функция проверяет значение переменной frame. Если указатель frame не инициализирован, функция создает новый фрейм и устанавливает две функции обратного вызова: UpdateDrag() и UpdateMorph(). Если указатель frame уже был инициализирован при предыдущем вызове функции, текущая сетка удаляется из сцены с помощью функции DeleteVisual() интерфейса Direct3DRMFrame.
Затем вызывается функция LoadMorphSequence(). Если она завершается неудачно (возвращает FALSE), немедленно осуществляется возврат из функции InitMorphSequence().
Потом в цикле устанавливаются ключи трансформации для каждого шага. Кроме того, инициализируются члены данных maxmorphtime и morphtime.
В конце вызывается функция MorphWin::GetMorphMesh() чтобы получить указатель на вновь созданную трансформируемую сетку. Цвет сетки изменяется и сетка добавляется к сцене.
Воспроизведением последовательности трансформаций управляет функция обратного вызова UpdateMorph():
void MorphPlayWin::UpdateMorph(LPDIRECT3DRMFRAME, void* ptr, D3DVALUE) { MorphPlayWin* win = (MorphPlayWin*)ptr; const D3DVALUE maxtime = win->maxmorphtime; const int morphspeed = win->morphspeed; const D3DVALUE morphtimeinc = win->morphtimeinc; if (morphspeed == MORPH_FORWARD) { morphtime += morphtimeinc; if (morphtime > maxtime) morphtime = D3DVALUE(0); } else if (morphspeed == MORPH_REVERSE) { morphtime -= morphtimeinc; if (morphtime < D3DVALUE(0)) morphtime = maxtime; } else if (morphspeed == MORPH_BOTH) { static BOOL forward = TRUE; if (forward) morphtime += morphtimeinc; else morphtime -= morphtimeinc; if (morphtime < D3DVALUE(0) || morphtime > maxtime) forward= 1 - forward; } win->SetMorphTime(morphtime); }
Значение времени для последовательности трансформаций вычисляется на основе переменной morphspeed. Если значение переменной morphspeed равно MORPH_FORWARD, то время в последовательности трансформаций непрерывно увеличивается, пока не достигнет максимально возможного значения. После этого отсчет времени снова начинается с нуля. Текущее время трансформации хранится в переменной morphtime.
Если значение переменной morphspeed равно MORPH_REVERSE, новое значение времени вычисляется путем вычитания заданного значения из переменной morphtime. Константа MORPH_BOTH означает, что последовательность трансформаций сначала воспроизводится в прямом направлении, а затем — в обратном.
После вычисления нового значения переменной morphtime вызывается функция MorphWin::SetMorphTime(). В результате выполняется вычисление новых данных вершин и производится обновление трансформируемой сетки.
В приложении MorphPlay есть четыре функции для реализации возможности вращения сетки: OnLButtonDown(), OnLButtonUp(), UpdateDrag() и OnIdle(). Давайте сначала взглянем на код функции OnLButtonDown():
void MorphPlayWin::OnLButtonDown(UINT nFlags, CPoint point) { if (!drag) { drag = TRUE; last_x = GetMouseX(); last_y = GetMouseY(); ShowCursor(FALSE); SetCapture(); } MorphWin::OnLButtonDown(nFlags, point); }
Функция начинает новую операцию перетаскивания, если только она уже не начата. Переменная drag используется для контроля текущего состояния операции перетаскивания. В начале операции перетаскивания сохраняется текущая позиция указателя мыши, сам указатель убирается с экрана, и мышь захватывается приложением с помощью функции MFC SetCapture(). После вызова функции SetCapture() все поступающие от мыши сообщения будут направляться приложению, которое захватило мышь, независимо от того, в каком месте экрана находится указатель мыши.
Функция OnLButtonUp() выглядит следующим образом:
void MorphPlayWin::OnLButtonUp(UINT nFlags, CPoint point) { if (drag) { end_drag = TRUE; ReleaseCapture(); ShowCursor(TRUE); } MorphWin::OnLButtonUp(nFlags, point); }
Сначала функция OnLButtonUp() проверяет, существует ли начатая операция перетаскивания. Если да, то переменной end_drag присваивается значение TRUE, что указывает на необходимость прекращения операции перетаскивания. Затем приложение освобождает мышь с помощью функции ReleaseCapture() и разрешает отображение указателя мыши.
Функция обратного вызова UpdateDrag() отвечает за вращение трансформируемой сетки при ее перетаскивании. Вот как выглядит код функции:
void MorphPlayWin::UpdateDrag(LPDIRECT3DRMFRAME frame, void*, D3DVALUE) { if (drag) { double delta_x, delta_y; int x = GetMouseX(); int y = GetMouseY(); delta_x = x - last_x; delta_y = y - last_y; last_x = x; last_y = y; double delta_r = sqrt(delta_x * delta_x + delta_y * delta_y); double radius = 50; double denom; denom = sqrt(radius * radius + delta_r * delta_r); if (!(delta_r == 0 || denom == 0)) frame->SetRotation(0, D3DDivide(D3DVAL((float)-delta_y), D3DVAL((float)delta_r)), D3DDivide(D3DVAL((float)-delta_x), D3DVAL((float)delta_r)), D3DVAL(0.0), D3DDivide(D3DVAL((float)delta_r), D3DVAL((float)denom))); } if (end_drag) { drag = FALSE; end_drag = FALSE; } }
Функция UpdateDrag() преобразует данные о перемещении мыши (сохраненные в переменных x, y, last_x и last_y) для вычисления вектора вращения сетки. Новые атрибуты вращения назначаются с помощью функции SetRotation() интерфейса Direct3DRMFrame.
В конце приведем код функции OnIdle(), которая используется для предотвращения беспорядочного вращения сетки в момент операции перетаскивания.
void MorphPlayWin::OnIdle(LONG) { if (drag && frame) frame->SetRotation(0, D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0)); }
netlib.narod.ru | < Назад | Оглавление | Далее > |