В этой главе вы узнали, как различными методами накладывать текстуры на сетки. Мы также обсудили декалы и прозрачность текстур. Теперь пришло время поговорить об анимации текстур.
Существует два варианта анимации текстур. В первом случае используется одна текстура, а при анимации меняется способ ее наложения. Во втором случае используется несколько накладываемых одна за другой текстур, образующих подобие кадров фильма. В данном разделе мы обсудим оба этих способа и начнем с анимации единственной текстуры.
Для анимации единственной текстуры есть масса возможностей. Чтобы модифицировать способ наложения текстуры на сетку можно изменять масштаб текстуры, начало координат, метод наложения и даже параметры прозрачности. Наиболее простой способ для перемещения текстуры по сетке— изменение начала координат текстуры.
Во всех рассмотренных до данного момента демонстрационных программах текстуры назначались интерфейсу Direct3DRMMeshBuilder, а затем конструктор сеток присоединялся к фрейму функцией AddVisual() интерфейса Direct3DRMFrame.
В этом разделе мы введем и обсудим понятие декала. Декал— это текстура, которая добавляется непосредственно к сцене. Декалы в сцене выглядят точно также, как и в графическом редакторе; они не могут быть обернуты вокруг объектов сцены. Можно перемещать и масштабировать декалы, но их нельзя вращать; декалы всегда повернуты к камере своей лицевой поверхностью. Этот факт ограничивает полезность декалов, но их все равно удобно применять для добавления к сцене двумерных элементов. Например, часто в приложениях с помощью декалов изображают взрывы, поскольку использование для изображения взрывов граней и сеток требует слишком большого количества ресурсов.
СОВЕТ | О декалах. Некоторые из функций интерфейса Direct3DRMTexture используют слово «decal», и декалы реализуются с помощью интерфейса Direct3DRMTexture. Интерфейса Direct3DRMDecal не существует. |
Демонстрационное приложение Jade отображает сетку, имеющую форму букв D3D и накладывает на эту сетку нефритовую текстуру. Сетка анимирована, чтобы зритель мог рассмотреть ее с разных сторон. Окно приложения Jade показано на рис. 5.3.
Рис. 5.3. Приложение Jade
В приложении Jade для наложения текстуры на сетку используется плоское покрытие. Это означает, что текстура накладывается на сетку прямолинейно, без изгибов или деформирования. Поэтому, когда направление взгляда на сетку совпадает с направлением наложения текстуры, последняя выглядит точно так же, как в графическом редакторе. Текстура, используемая в приложении Jade показана на рис. 5.4.
Рис. 5.4. Текстура, используемая в приложении Jade
Приложение Jade демонстрирует следующие технологии:
Загрузка сетки и текстуры из ресурсов программы. Большинство демонстрационных приложений на CD-ROM во время выполнения загружают сетки и/или текстуры. Чтобы исключить риск того, что требуемый файл будет недоступен, каждый из примеров хранит необходимые файлы как ресурсы. Если вы хотите модифицировать примеры так, чтобы вместо ресурсов использовались файлы на диске, обратитесь к приложению Sample из главы 4 или исследуйте код, создаваемый мастером Direct3D AppWizard. Использование интерфейса Direct3DRMWrap для наложения текстуры на сетку. Использование команд меню для изменения параметров визуализации во время выполнения программы. Использование функций обратного вызова для анимации объекта.Приложение Jade (как и большинство демонстрационных программ на CD-ROM) позволяет во время работы программы изменять метод визуализации сетки. Эту возможность обеспечивают функции OnRenderWireframe(), OnRenderFlat() и OnRenderGouraud(), вызываемые MFC при выборе пользователем команд из меню Render. Эти три функции выглядят следующим образом:
void JadeWin::OnRenderWireframe() { if (meshbuilder) meshbuilder->SetQuality(D3DRMRENDER_WIREFRAME); } void JadeWin::OnRenderFlat() { if (meshbuilder) meshbuilder->SetQuality(D3DRMRENDER_FLAT); } void JadeWin::OnRenderGouraud() { if (meshbuilder) meshbuilder->SetQuality(D3DRMRENDER_GOURAUD); }В каждой функции для задания метода визуализации применяется метод SetQuality() интерфейса Direct3DRMMeshBuilder. Еще три функции используются для отображения метки слева от используемого в данный момент метода визуализации в меню Render. Текст функций OnUpdateRenderFlat(), OnUpdateRenderGouraud() и OnUpdateRenderWireframe() приведен ниже:
void JadeWin::OnUpdateRenderWireframe(CCmdUI* pCmdUI) { if (meshbuilder) { D3DRMRENDERQUALITY meshquality = meshbuilder->GetQuality(); pCmdUI->SetCheck(meshquality == D3DRMRENDER_WIREFRAME); } } void JadeWin::OnUpdateRenderFlat(CCmdUI* pCmdUI) { if (meshbuilder) { D3DRMRENDERQUALITY meshquality = meshbuilder->GetQuality(); pCmdUI->SetCheck(meshquality == D3DRMRENDER_FLAT); } } void JadeWin::OnUpdateRenderGouraud(CCmdUI* pCmdUI) { if (meshbuilder) { D3DRMRENDERQUALITY meshquality = meshbuilder->GetQuality(); pCmdUI->SetCheck(meshquality == D3DRMRENDER_GOURAUD); } }MFC вызывает каждую из этих трех функций при выводе на экран меню Render. Каждая из функций проверяет текущие параметры визуализации сетки и указывает MFC необходимо ли ставить флажок около данного пункта меню.
Почти все демонстрационные программы на CD-ROM предоставляют меню, подобные меню Render. Функции поддержки команд меню, подобные рассмотренным выше, могут быть добавлены в программу или удалены из нее с помощью Visual C++ ClassWizard.
Функция CreateScene() приложения OrbStar устанавливает две функции обратного вызова: одну для фрейма к которому присоединена сетка звезды и другую для фрейма с сеткой сферы. Эти функции периодически назначают каждому фрейму случайные атрибуты вращения, вызывая изменение скорости и направления вращения сеток в сцене приложения. Функции обратного вызова MoveStar() и MoveSphere() практически идентичны, поэтому здесь приводится только код функции MoveStar().
void OrbStarWin::MoveStar(LPDIRECT3DRMFRAME frame, void*, D3DVALUE) { static UINT delay; if (++delay < 11) return; delay = 0; LPDIRECT3DRMFRAME scene; frame->GetScene(&scene); D3DVECTOR spinvect; D3DRMVectorRandom(&spinvect); D3DVALUE spin = D3DDivide(rand() %100 + 1, 200); frame->SetRotation(scene, spinvect.x, spinvect.y, spinvect.z, spin); }Функция MoveStar() использует статическую переменную delay в качестве счетчика, регулирующего частоту изменения атрибутов вращения фрейма. В нашем случае изменение происходит, когда значение счетчика станет равно 11.
Как только станет ясно, что необходимо вычислить новые атрибуты вращения, с помощью функции GetScene() интерфейса Direct3DRMFrame мы получаем указатель на корневой фрейм сцены. Вспомните, что функции обратного вызова должны быть объявлены как статические из-за того, что для вызова обычных функций класса необходим указатель на класс. Статические функции класса не могут обращаться к переменным класса (если только эти переменные тоже не объявлены статическими). Если бы MoveStar() не была статической, мы могли бы использоать переменную RMWin::scene так же, как это делается в функции CreateScene(). К счастью Direct3D передает в первом параметре функции обратного вызова указатель на фрейм, для которого эта функция вызвана, и предоставляет функцию GetScene(). Нам потребуется указатель на фрейм сцены, когда позднее из этой функции мы вызовем функцию SetRotation().
Функция MoveStar() вычисляет новые атрибуты вращения случайным образом. Функция D3DRMVectorRandom() возвращает случайный вектор, а скорость вращения вычисляется с помощью функции rand(). Новые атрибуты вращения фрейма устанавливаются функцией SetRotation().
В отличие от приложения Wraps, вся функциональность приложения Decal предоставляется функцией CreateScene(). Эта функция создает и настраивает декалы, источник света и порт просмотра. Поскольку анимация в приложении Decal реализуется с помощью атрибутов движения, никакие функции обратного вызова не требуются. Код функции CreateScene() представлен в листинге 5.3.
Листинг 5.3. Функция DecalWin::CreateScene() |
BOOL DecalWin::CreateScene() { //------ ДЕКАЛЫ -------- LPDIRECT3DRMTEXTURE texture1, texture2; HRSRC texture_id; texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE1), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture1); texture1->SetDecalOrigin(64, 64); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE2), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture2); texture2->SetDecalOrigin(64, 64); //-------- ФРЕЙМЫ -------- LPDIRECT3DRMFRAME dummyframe1; d3drm->CreateFrame(scene, &dummyframe1); dummyframe1->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0.05)); LPDIRECT3DRMFRAME dummyframe2; d3drm->CreateFrame(scene, &dummyframe2); dummyframe2->SetRotation(scene, D3DVALUE(1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(-0.05)); LPDIRECT3DRMFRAME orbitframe1; d3drm->CreateFrame(dummyframe1, &orbitframe1); orbitframe1->SetPosition(dummyframe1, D3DVALUE(2), D3DVALUE(0), D3DVALUE(0)); orbitframe1->AddVisual(texture1); LPDIRECT3DRMFRAME orbitframe2; d3drm->CreateFrame(dummyframe2, &orbitframe2); orbitframe2->SetPosition(dummyframe2, D3DVALUE(0), D3DVALUE(-2), D3DVALUE(0)); orbitframe2->AddVisual(texture2); texture1->Release(); texture1 = 0; texture2->Release(); texture2 = 0; dummyframe1->Release(); dummyframe1 = 0; dummyframe2->Release(); dummyframe2 = 0; orbitframe1->Release(); orbitframe1 = 0; orbitframe2->Release(); orbitframe2 = 0; //--------- СВЕТ -------- LPDIRECT3DRMLIGHT light; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1.0),D3DVALUE(1.0), D3DVALUE(1.0), &light); scene->AddLight(light); light->Release(); light = 0; //--------- ПОРТ ПРОСМОТРА -------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(-6.0)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; } |
Функция CreateScene() выполняет следующие четыре действия:
Создание двух текстур, которые будут использоваться в качестве декалов. Создание фреймов для двух текстур. Создание источника света. Создание порта просмотра.Первый этап — загрузка двух текстур. Как и в большинстве демонстрационных приложений, текстуры хранятся в ресурсах в исполняемом файле приложения:
LPDIRECT3DRMTEXTURE texture1, texture2; HRSRC texture_id; texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE1), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture1); texture1->SetDecalOrigin(64, 64); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE2), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture2); texture2->SetDecalOrigin(64, 64);После загрузки каждой из текстур вызывается функция SetDecalOrigin(). Она применяется для указания точки внутри текстуры, которая будет использоваться как начало координат. По умолчанию началом координат текстуры считается верхний левый угол (x = 0, y = 0). Это означает, что при присоединении текстуры к фрейму с заданным местоположением фрейма будет совпадать верхний левый угол текстуры. В приложении Decal используются две текстуры размером 128 на 128 точек. Используя в качестве аргументов функции SetDecalOrigin() число 64, мы помещаем начало координат текстуры в ее центр.
Затем в приложении создаются фреймы (как пустые, так и фреймы для текстур):
LPDIRECT3DRMFRAME dummyframe1; d3drm->CreateFrame(scene, &dummyframe1); dummyframe1->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0.05)); LPDIRECT3DRMFRAME dummyframe2; d3drm->CreateFrame(scene, &dummyframe2); dummyframe2->SetRotation(scene, D3DVALUE(1), D3DVALUE(0), D3DVALUE(0), D3DVALUE(-0.05)); LPDIRECT3DRMFRAME orbitframe1; d3drm->CreateFrame(dummyframe1, &orbitframe1); orbitframe1->SetPosition(dummyframe1, D3DVALUE(2), D3DVALUE(0), D3DVALUE(0)); orbitframe1->AddVisual(texture1); LPDIRECT3DRMFRAME orbitframe2; d3drm->CreateFrame(dummyframe2, &orbitframe2); orbitframe2->SetPosition(dummyframe2, D3DVALUE(0), D3DVALUE(-2), D3DVALUE(0)); orbitframe2->AddVisual(texture2);Для каждого пустого фрейма функцией SetRotation() задаются параметры вращения, но сами фреймы не перемещаются из своего начального местоположения (начала координат). Перемещающиеся по орбите фреймы смещаются на заданное расстояние от пустых фреймов с помощью функции SetPosition(). Обратите внимание, что пустые фреймы являются потомками фрейма scene (корня иерархии фреймов), а перемещающиеся по орбитам фреймы являются потомками своих пустых фреймов. Созданные ранее текстуры присоединяются к помещенным на орбиту фреймам функцией AddVisual(). Тот факт, что текстура присоединяется непосредственно к фрейму и делает ее декалом.
На третьем и четвертом этапе для приложения Decal создаются источник света и порт просмотра. Используемый для этого код аналогичен тому, который мы видели в приложении Jade. Далее в этой книге мы более подробно изучим источники света и порты просмотра.
Код функции CreateScene() приложения Jade приведен в листинге 5.1.
Листинг 5.1. Функция JadeWin::CreateScene() |
BOOL JadeWin::CreateScene() { HRESULT r; //------- ФОН -------- scene->SetSceneBackgroundRGB(D3DVALUE(.2), D3DVALUE(.2), D3DVALUE(.2)); //-------- СЕТКА -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_D3DMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->SetPerspective(TRUE); r = meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); if (r != D3DRM_OK) { TRACE("meshbuilder->Load() failed\n"); return FALSE; } ScaleMesh(meshbuilder, D3DVALUE(35)); //-------- ТЕКСТУРА -------- LPDIRECT3DRMTEXTURE texture; HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_JADETEXTURE), "TEXTURE"); r = d3drm->LoadTextureFromResource(texture_id, &texture); if (r != D3DRM_OK) { TRACE("d3drm->LoadTextureFromResource() failed\n"); return FALSE; } meshbuilder->SetTexture(texture); texture->Release(); texture = 0; //-------- НАЛОЖЕНИЕ -------- D3DRMBOX box; meshbuilder->GetBox(&box); D3DVALUE w = box.max.x - box.min.x; D3DVALUE h = box.max.y - box.min.y; LPDIRECT3DRMWRAP wrap; d3drm->CreateWrap(D3DRMWRAP_FLAT, scene, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), // начало координат наложения D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0), // ось z наложения D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0), // ось y наложения D3DVALUE(0.5), D3DVALUE(0.5), // начало координат текстуры D3DDivide(1,w), D3DDivide(1,h), // масштаб текстуры &wrap); wrap->Apply(meshbuilder); wrap->Release(); wrap = 0; //------- ФРЕЙМ СЕТКИ ---------- LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->AddMoveCallback(MoveFrame, NULL); meshframe->Release(); //---------- ОСВЕЩЕНИЕ ----------- LPDIRECT3DRMLIGHT light; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1),D3DVALUE(1), D3DVALUE(1), &light); scene->AddLight(light); light->Release(); light = 0; //---------- КАМЕРА ----------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(-50.0)); d3drm->CreateViewport( device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; } |
Функция CreateScene() выполняет следующие действия:
Изменение цвета фона сцены. Загрузка сетки. Загрузка нефритовой текстуры. Создание и применение наложения текстуры. Создание фрейма для размещения сетки. Создание источника рассеянного света. Создание порта просмотра.Давайте рассмотрим каждый из этих этапов по отдельности. Первое, что делает функция CreateScene() — это изменение цвета фона сцены с помощью функции SetSceneBackgroundRGB():
scene->SetSceneBackgroundRGB(D3DVALUE(.2), D3DVALUE(.2), D3DVALUE(.2));Функция SetSceneBackgroundRGB() получает три аргумента, определяющие красную, зеленую и синюю составляющие нового цвета фона сцены. Для нашего примера мы выбрали темно-серый цвет.
Затем для загрузки сетки и настройки ее параметров используется интерфейс Direct3DRMMeshBuilder:
D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_D3DMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->SetPerspective(TRUE); r = meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); if (r != D3DRM_OK) { TRACE("meshbuilder->Load() failed\n"); return FALSE; } ScaleMesh(meshbuilder, D3DVALUE(35));Для загрузки сетки из ресурсов программы необходимо подготовить структуру D3DRMLOADRESOURCE, содержащую три поля: hModule, lpName и lpType. Поле hModule идентифицирует модуль, содержащий требуемый ресурс. Это полезно, если ресурсы загружаются из другого исполняемого модуля, но поскольку в нашем случае требуемый ресурс относится к той же программе, которая вызывает функцию, мы укажем NULL. Поле lpName хранит значение, идентифицирующее требуемый ресурс. Поле lpType задает тип ресурса.
СОВЕТ | Тип ресурса MESH. Нет ничего необычного в типе ресурса MESH (Visual C++ ничего не знает о сетках Direct3D). Приложения из этой книги хранят сетки в группе ресурсов с именем MESH, чтобы отделить ресурсы сеток от других ресурсов. |
После того, как подготовлена структура D3DRMLOADRESOURCE, функция CreateMeshBuilder() интерфейса Direct3DRM инициализирует указатель meshbuilder. Функция SetPerspective() разрешает коррекцию перспективы для новой сетки. Коррекция перспективы предотвращает смещение текстуры во время анимации (чтобы увидеть этот эффект, закоменнтируйте данную строку кода и откомпилируйте пример заново).
Затем вызывается функция Load(), которой в качестве первого аргумента передается указатель на подготовленную структуру D3DRMLOADRESOURCE. Третий аргумент функции Load() (флаг D3DRMLOAD_FROMRESOURCE) указывает Direct3D, что загрузка происходит из ресурсов программы, а не из файла на диске. Возвращаемое функцией Load() значение, говорит о том, успешно ли завершилась функция. Если при выполнении функции Load() произошла ошибка, CreateScene() возвращает FALSE.
СОВЕТ | Проверка возвращаемых значений. Большинство функций Direct3D возвращают значение типа HRESULT, указывающее на состояние функции. Как правило, не требуется проверять возвращаемое значение для каждой функции, но рекомендуется проверять возвращаемые значения для тех функций, которые могут завершиться неудачно из-за внешних причин. Например, функции загрузки файла часто завершаются неудачно, из-за того, что не могут найти требуемый файл. В данном сучае неудачное завершение функции Load() маловероятно, поскольку сетка является частью EXE-файла программы. |
Затем используется функция ScaleMesh() для указания идеального размера сетки. Первый аргумент ScaleMesh() — Direct3DRMMeshBuilder представляет масштабируемую сетку. Второй аргумент — это желаемый размер сетки.
На следующем (третьем) этапе выполняется загрузка текстуры. Загрузка тексуры из ресурсов программы слегка отличается от загрузки сеток. Для загрузки сеток применяется функция Load(), независимо от того, где находится сетка: в файле на диске или в ресурсах программы. Для загрузки текстуры из ресурсов и из файла применяются различные функции. Мы воспользуемся функцией LoadTextureFromResource() (вместо функции LoadTexture()):
LPDIRECT3DRMTEXTURE texture; HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_JADETEXTURE), "TEXTURE"); r = d3drm->LoadTextureFromResource(texture_id, &texture); if (r != D3DRM_OK) { TRACE("d3drm->LoadTextureFromResource() failed\n"); return FALSE; } meshbuilder->SetTexture(texture); texture->Release(); texture = 0;Функция LoadTextureFromResource() получает два аргумента: экземпляр структуры HRSRC и адрес указателя на интерфейс Direct3DRMTexture. HRSRC — это структура Win32, подобная структуре D3DRMLOADRESOURCE используемой функцией Load() интерфейса конструктора сеток. Структура HRSRC инициализируется функцией FindResource(). Три аргумента FindResource() идентифицируют загружаемый ресурс. Первый аргумент — это дескриптор модуля. Поскольку текстура загружается из того же исполняемого файла в котором содержится функция CreateScene(), мы используем значение NULL. Второе значение — это идентификатор загружаемого ресурса. Третий аргумент указывает тип ресурса.
После инициализации структуры HRSRC вызывается функция LoadTextureFromResource(). Если функция LoadTextureFromResource() завершается с ошибкой, CreateScene() возвращает FALSE (после того, как макрос TRACE выведет сообщение). Если функция завершилась успешно, то текстура присоединяется к ранее загруженной сетке с помощью функции SetTexture() интерфейса Direct3DRMMeshBuilder. Затем указатель texture освобождается, поскольку он больше нам не потребуется. После того, как указатель освобожден, ему присваивается нулевое значение.
СОВЕТ | Остерегайтесь висящих указателей. Я рекомендую вам избегать висящих указателей, присваивая им нулевые значения после освобождения. Эта привычка спасет вас от проблем, которые могут возникнуть, если вы нечаянно используете освобожденный указатель. |
Функция SetTexture() не указывает, как должна быть наложена текстура, она только сообщает, что текстура накладывается на сетку. Чтобы определить способ покрытия текстурой необходимо использовать наложение текстуры. На этапе 4 мы создадим и применим плоское наложение текстуры чтобы текстура не изгибалась и не деформировалась, а прямо накладывалась на сетку. Нам потребуется масштабировать текстуру, чтобы она была такого же размера, как и сетка. Чтобы сделать это, нам необходимо узнать размер сетки. Приведенный ниже код расположен в функции CreateScene() перед созданием наложения текстуры:
D3DRMBOX box; meshbuilder->GetBox(&box); D3DVALUE w = box.max.x - box.min.x; D3DVALUE h = box.max.y - box.min.y;Функция GetBox() применяется для заполнения структуры D3DRMBOX, содержащей координаты крайних точек сетки. Мы используем эти значения для вычисления длины и высоты сетки. Обратите внимание, что мы вычисляем длину и высоту сетки, но не глубину. Поскольку текстура является двумерной, третье измерение сетки нас не интересует.
Теперь мы можем создать и применить наложение текстуры:
LPDIRECT3DRMWRAP wrap; d3drm->CreateWrap(D3DRMWRAP_FLAT, scene, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), // начало координат наложения D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0), // ось z D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0), // ось y D3DVALUE(0.5), D3DVALUE(0.5), // начало координат текстуры D3DDivide(1,w), D3DDivide(1,h), // масштаб текстуры &wrap); wrap->Apply(meshbuilder); wrap->Release(); wrap = 0;Для создания наложения мы используем функцию CreateWrap() интерфейса Direct3DRM. Функция CreateWrap() получает 16 аргументов. Первый аргумент определяет способ наложения. Мы используем константу D3DRMWRAP_FLAT чтобы указать на использование плоского наложения текстуры. Второй аргумент задает базовый фрейм. Мы укажем здесь корневой фрейм сцены, чтобы значения остальных аргументов воспринимались как абсолютные координаты. Если мы укажем другой фрейм, то оставшиеся аргументы будут интерпретироваться как координаты относительно указанного фрейма.
СОВЕТ | Задание абсолютных значений. Существует два способа указать Direct3D, что заданные значения являются абсолютными, а не относительными. Первый способ — указать в качестве базового корневой фрейм сцены (как сделано в рассматриваемом коде). Второй — передать вместо указателя на базовый фрейм NULL. |
Следующие девять аргументов CreateWrap() определяют местоположение и ориентацию наложения. Используемые в функции CreateScene() значения представлены в таблице 5.1.
Таблица 5.1.Параметры наложения для приложения Jade
Параметр | Значение |
Начало координат наложения | <0.0, 0.0, 0.0> |
Ось Z наложения | <0.0, 0.0, 1.0> |
Ось Y наложения | <0.0, 1.0, 0.0> |
Первые три аргумента определяют начало координат наложения. Используя значения <0.0, 0.0, 0.0>, мы указываем, что наложение размещается на локальных осях сетки (локальное начало координат для сетки обычно совпадает с ее центром).
Следующие три аргумента определяют вектор, задающий направление наложения. В данном примере мы используем вектор, совпадающий с осью Z и направленный от зрителя. Третий набор из трех аргументов задает вектор, определяющий направление вверх для покрытия. Мы используем направленный вверх вектор, который совпадает с осью Y.
Следующие четыре аргумента CreateWrap() задают начало координат и масштаб для текстуры. Параметры, используемые в функции CreateScene() представлены в таблице 5.2.
Таблица 5.2.Параметры текстуры для приложения Jade
Параметр | Значение |
Начало координат текстуры | <0.5, 0.5> |
Масштаб текстуры | <D3DDivide(1,w), D3DDivide(1,h)> |
Начало координат текстуры определяет точку текстуры, которая будет совпадать с началом координат наложения. Мы используем значение 0.5 и для координаты X и для координаты Y текстуры, чтобы начало координат наложения совпадало с центром текстуры (значение 0.5 означает середину текстуры, 0.0 означает верхний или левый край текстуры, а 1.0 означает правый или нижний край текстуры).
Масштаб текстуры определяет размер текстуры в пропорции к наложению. Вспомните, что мы собирались масштабировать текстуру так, чтобы она точно соответствовала размерам сетки. Мы уже отцентрировали текстуру, задав значения начала ее координат равными 0.5. Теперь, используя значения ширины и высоты сетки, мы масштабируем текстуру, чтобы она соответствовала размерам сетки. Для удобства Direct3D предоставляет макрос D3DDivide. Этот макрос приводит свои аргументы к типу D3DVALUE и делит первый аргумент на второй. Благодаря этому мы получаем коэффициент масштабирования, необходимый чтобы размеры текстуры после масштабирования соответствовали размерам сетки.
Последний аргумент CreateWrap() — это адрес указателя, который после выполненой функцией инициализации будет указывать на интерфейс Direct3DRMWrap. После того, как наложение создано, оно может быть применено к сетке с помощью функции Apply() интерфейса Direct3DRMWrap:
wrap->Apply(meshbuilder);Функция Apply() вычисляет метод, которым текстура будет накладываться, для каждой из граней сетки. После вызова функции Apply() указатель wrap освобождается.
На пятом этапе создается фрейм для сетки:
LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->AddMoveCallback(MoveFrame, NULL); meshframe->Release(); meshframe = 0;Функция CreateFrame() интерфейса Direct3DRM применяется для создания фрейма с именем meshframe. Конструктор сеток присоединяется к новому фрейму функцией AddVisual(). Затем с помощью функции AddMoveCallback() устанавливается функция обратного вызова MoveFrame(). После описаных действий указатель meshframe освобождается.
Шестой этап — это создание источника рассеянного света:
LPDIRECT3DRMLIGHT light; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1),D3DVALUE(1), D3DVALUE(1), &light); scene->AddLight(light); light->Release(); light = 0;Источник рассеянного света применяется в нашем примере по причине простоты использования. Более подробно об источниках света мы поговорим в главе 6.
Седьмой и последний этап выполнения функции CreateScene() — создание порта просмотра:
d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(-50.0)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);Переменные camera и viewport наследуются от класса RMWin и должны инициализироваться в функции CreateScene(). Переменная camera представляет собой указатель на интерфейс Direct3DRMFrame, инициализируемый функцией CreateFrame() интерфейса Direct3DRM. С помощью функции SetPosition() новый фрейм располагается на 50 единиц дальше начала координат. После этого функция CreateViewport() интерфейса Direct3DRM инициализирует указатель viewport.
Чтобы сообщить, что сцена успешно создана, функция CreateScene() возвращает TRUE. Если функция CreateScene() возвращает FALSE, выполнение приложения прекращается и выводится соответствующее сообщение.
MoveFrame()— это функция обратного вызова, управляющая анимацией сетки. Текст функции приведен ниже:
void JadeWin::MoveFrame(LPDIRECT3DRMFRAME frame, void*, D3DVALUE) { static int x; static int y; static int xi = 7; static int yi = 13; if (x < -31 || x > 31) xi = -xi; if (y < -35 || y > 35) yi = -yi; x += xi; y += yi; frame->SetOrientation(NULL, D3DVALUE(x), D3DVALUE(y), D3DVALUE(50), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); }Функция MoveFrame() использует простой алгоритм «подскакивающего мяча» для изменения ориентации фрейма к которому прикреплена сетка при каждом обновлении экрана. Статические целочисленные переменные используются для запоминания текущей позиции фрейма и для вычисления новой. После того, как новая позиция вычислена, она назначается фрейму с помощью функции SetOrientation() интерфейса Direct3DRMFrame.
Функция CreateScene() приложения OrbStar создает сетку звезды и сетку сферы, текстуру, которая будет наложена на сферу, два источника света и порт просмотра. Код функции CreateScene() приведен в листинге5.4.
Листинг 5.4. Функция OrbStarWin::CreateScene() |
BOOL OrbStarWin::CreateScene() { //-------- СЕТКА ЗВЕЗДЫ -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_STARMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER starbuilder; d3drm->CreateMeshBuilder(&starbuilder); starbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); starbuilder->SetColorRGB(D3DVALUE(1.0), D3DVALUE(0.0), D3DVALUE(0.0)); ScaleMesh(starbuilder, D3DVALUE(20)); //--------- ФРЕЙМ ЗВЕЗДЫ -------- LPDIRECT3DRMFRAME starframe; d3drm->CreateFrame(scene, &starframe); starframe->SetRotation(scene, D3DVALUE(1.0), D3DVALUE(0.0),D3DVALUE(0.0), D3DVALUE(0.1)); starframe->AddVisual(starbuilder); starframe->AddMoveCallback(MoveStar, NULL); starframe->Release(); starframe = 0; starbuilder->Release(); starbuilder = 0; //--------- СЕТКА СФЕРЫ -------- resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER spherebuilder; d3drm->CreateMeshBuilder(&spherebuilder); spherebuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); spherebuilder->SetPerspective(TRUE); ScaleMesh(spherebuilder, D3DVALUE(25)); //---------- ТЕКСТУРА ДЛЯ СФЕРЫ ------ LPDIRECT3DRMTEXTURE texture; HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE( IDR_TRANSTEXTURE), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture); texture->SetDecalTransparency(TRUE); spherebuilder->SetTexture(texture); texture->Release(); texture = 0; //---------- НАЛОЖЕНИЕ ТЕКСТУРЫ ДЛЯ СФЕРЫ -------- D3DRMBOX box; spherebuilder->GetBox(&box); D3DVALUE width = box.max.x - box.min.x; D3DVALUE height = box.max.y - box.min.y; LPDIRECT3DRMWRAP wrap; d3drm->CreateWrap(D3DRMWRAP_FLAT, NULL, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), // начало координат D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0), // ось z D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0), // ось y D3DVALUE(0.5), D3DVALUE(0.5), // начало координат D3DDivide(1,width), D3DDivide(1,height), // масштаб &wrap); wrap->Apply(spherebuilder); wrap->Release(); wrap = 0; //-------- ФРЕЙМ СФЕРЫ ---------- LPDIRECT3DRMFRAME sphereframe; d3drm->CreateFrame(scene, &sphereframe); sphereframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.1)); sphereframe->AddVisual(spherebuilder); sphereframe->AddMoveCallback(MoveSphere, NULL); sphereframe->Release(); sphereframe = 0; spherebuilder->Release(); spherebuilder = 0; //----------- СВЕТ -------- LPDIRECT3DRMLIGHT light1, light2; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(0.8),D3DVALUE(0.8), D3DVALUE(0.8), &light1); d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(0.9), D3DVALUE(0.9), D3DVALUE(0.9), &light2); LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->SetOrientation(scene, D3DVALUE(-1), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); lightframe->AddLight(light1); lightframe->AddLight(light2); lightframe->Release(); lightframe = 0; light1->Release(); light1 = 0; light2->Release(); light2 = 0; //----------- ПОРТ ПРОСМОТРА -------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0.0), D3DVALUE(0.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_STARMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER starbuilder; d3drm->CreateMeshBuilder(&starbuilder); starbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); starbuilder->SetColorRGB(D3DVALUE(1.0), D3DVALUE(0.0), D3DVALUE(0.0)); ScaleMesh(starbuilder, D3DVALUE(20));Как и в других демонстрационных приложениях, для загрузки сетки из ресурсов программы используется функция Load() интерфейса Direct3DRMMeshBuilder. Как только сетка загружена, вызывается функция SetColorRGB() для окрашивания всей сетки в красный цвет. Затем для масштабирования сетки в соответствии с заданным размером применяется функция ScaleMesh().
Теперь создается фрейм (с именем starframe) и ему назначаются атрибуты вращения:
LPDIRECT3DRMFRAME starframe; d3drm->CreateFrame(scene, &starframe); starframe->SetRotation(scene, D3DVALUE(1.0), D3DVALUE(0.0),D3DVALUE(0.0), D3DVALUE(0.1)); starframe->AddVisual(starbuilder); starframe->AddMoveCallback(MoveStar, NULL); starframe->Release(); starframe = 0; starbuilder->Release(); starbuilder = 0;Новый фрейм получает атрибуты вращения, задающие его вращение вокруг оси X. Как вы увидите, эти атрибуты являются временными, поскольку мы используем функцию обратного вызова, для изменения атрибутов вращения во время работы приложения. Созданный ранее конструктор сеток присоединяется к новому фрейму с помощью функции AddVisual(). Перед освобождением указателей starframe и starbuilder выполняется установка функции обратного вызова MoveStar(). На код функции MoveStar() мы взглянем чуть позже. После выполнения описанных действий выполняется освобождение указателей starframe и starbuilder.
Далее создается и загружается сетка сферы:
resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER spherebuilder; d3drm->CreateMeshBuilder(&spherebuilder); spherebuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); spherebuilder->SetPerspective(TRUE); ScaleMesh(spherebuilder, D3DVALUE(25));После загрузки сетки вызывается функция SetPerspective() чтобы разрешить перспективную коррекцию для сетки сферы. Мы не делали этого для сетки звезды, поскольку на нее не накладывается текстура. Далее сетка сферы масштабируется, чтобы ее размеры слегка превышали размеры сетки звезды.
Этап 4 — это создание текстуры для сферы:
LPDIRECT3DRMTEXTURE texture; HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE( IDR_TRANSTEXTURE), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture); texture->SetDecalTransparency(TRUE); spherebuilder->SetTexture(texture); texture->Release(); texture = 0;Как и в других приложениях, для создания текстуры используется функция LoadTextureFromResource(). Потом, чтобы разрешить прозрачность текстуры вызывается функция SetDecalTransparency(). Мы не определяем прозрачный цвет, поэтому по умолчанию как прозрачный будет восприниматься черный цвет. Для привязки текстуры к сетке сферы применяется функция SetTexture() интерфейса Direct3DRMMeshBuilder. После выполнения этих действий указатель texture освобождается.
Теперь для применения новой текстуры к сетке сферы создается наложение текстуры:
D3DRMBOX box; spherebuilder->GetBox(&box); D3DVALUE width=box.max.x-box.min.x; D3DVALUE height=box.max.y-box.min.y; LPDIRECT3DRMWRAP wrap; d3drm->CreateWrap( D3DRMWRAP_FLAT, NULL, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), // начало координат D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0), // ось z D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0), // ось y D3DVALUE(0.5), D3DVALUE(0.5), // начало координат D3DDivide(1,width), D3DDivide(1,height), // масштаб &wrap); wrap->Apply(spherebuilder); wrap->Release(); wrap = 0;В данном примере используется плоское наложение текстуры, а размеры сетки используются, чтобы размер покрытия соответствовал сетке. Функция Apply() интерфейса Direct3DRMWrap вызывается, чтобы применить покрытие текстурой к сетке сферы.
На шестом шаге создается фрейм для сетки сферы:
LPDIRECT3DRMFRAME sphereframe; d3drm->CreateFrame(scene, &sphereframe); sphereframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.1)); sphereframe->AddVisual(spherebuilder); sphereframe->AddMoveCallback(MoveSphere, NULL); sphereframe->Release(); sphereframe = 0; spherebuilder->Release(); spherebuilder = 0;Новому фрейму присваиваются атрибуты вращения, но, также как и для фрейма, созданного на этапе 2, для изменения этих атрибутов используется функция обратного вызова. С помощью функции AddVisual() к фрейму присоединяется сетка сферы и устанавливается функция обратного вызова MoveSphere(). Затем выполняется освобождение указателей sphereframe и spherebuilder.
На седьмом и восьмом этапах для сцены создаются источники света и порт просмотра. Код очень похож на тот, который используется в приложении Jade. Вы узнаете больше об источниках света и портах просмотра в следующих главах книги.
Во время конструирования сцены приложения ShowRoom, функция CreateScene() загружает 15 текстур, на каждой из которых изображена машина, повернутая под другим углом. Потом создаются сетка и плоское наложение текстуры, и первая текстура накладывается на сетку. Функция обратного вызова используется для последовательного наложения на сетку остальных текстур. Код функции CreateScene() приложения ShowRoom приведен в листинге5.7.
Листинг 5.7. Функция ShowRoomWin::CreateScene() |
BOOL ShowRoomWin::CreateScene() { //-------- ТЕКСТУРЫ -------- HRSRC texture_id; int t = 0; texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE01), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE02), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE03), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE04), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE05), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE06), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE07), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE08), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE09), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE10), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE11), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE12), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE13), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE14), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE15), "TEXTURE"); d3drm->LoadTextureFromResource(texture_id, &texture[t++]); // ------- КОНСТРУКТОР СЕТОК -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE( IDR_BOXMESH ); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER meshbuilder; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->Scale(D3DVALUE(1), D3DVALUE(1), D3DVALUE(.1)); ScaleMesh(meshbuilder, D3DVALUE(20)); meshbuilder->SetPerspective(TRUE); meshbuilder->SetQuality(D3DRMRENDER_FLAT); meshbuilder->SetTexture(texture[0]); //--------- НАЛОЖЕНИЕ -------- D3DRMBOX box; meshbuilder->GetBox(&box); D3DVALUE width = box.max.x - box.min.x; D3DVALUE height = box.max.y - box.min.y; LPDIRECT3DRMWRAP wrap; d3drm->CreateWrap(D3DRMWRAP_FLAT, NULL, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), // начало координат наложения D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0), // ось z D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0), // ось y D3DVALUE(0.5), D3DVALUE(0.5), // начало координат D3DDivide(1,width),D3DDivide(1,height), // масштаб &wrap); wrap->Apply(meshbuilder); wrap->Release(); wrap = 0; //------- СЕТКА ------ meshbuilder->CreateMesh(&mesh); meshbuilder->Release(); meshbuilder = 0; //------- ФРЕЙМ -------- LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(mesh); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0.05)); meshframe->AddMoveCallback(UpdateTexture, NULL); meshframe->Release(); meshframe = 0; //-------- СВЕТ -------- LPDIRECT3DRMLIGHT dlight, alight; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.0), D3DVALUE(1.0), D3DVALUE(1.0), &dlight); d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1.0), D3DVALUE(1.0), D3DVALUE(1.0), &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); lightframe->Release(); lightframe = 0; dlight->Release(); dlight = 0; alight->Release(); alight = 0; //------ КАМЕРА ---------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; } |
Функция CreateScene() выполняет следующие действия:
Создание пятнадцати текстур. Создание и конфигурирование конструктора сеток. Создание и применение плоского наложения текстуры. Создание интерфейса Direct3DRMMesh. Создание фрейма для сетки. Создание источника света. Создание порта просмотра.Первая часть функции CreateScene() весьма прямолинейна (и скучна). Создаются и загружаются 15 текстур, и указатель на каждую из текстур сохраняется в массиве texture.
Затем с помощью интерфейса Direct3DRMMeshBuilder загружается сетка куба. Для масштабирования куба применяется функция Scale():
D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE( IDR_BOXMESH ); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER meshbuilder; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->Scale(D3DVALUE(1), D3DVALUE(1), D3DVALUE(.1)); ScaleMesh(meshbuilder, D3DVALUE(20)); meshbuilder->SetPerspective(TRUE); meshbuilder->SetQuality(D3DRMRENDER_FLAT); meshbuilder->SetTexture(texture[0]);Это делается потому, что мы хотим для наложения текстур использовать почти плоскую прямоугольную сетку. Вместо того, чтобы создавать прямоугольную сетку, мы используем куб и масштабируем его по оси Z до одной десятой его исходного размера. Потом выполняется настройка параметров новой сетки для включения перспективной коррекции и использования при визуализации метода равномерной закраски. Первая текстура из массива texture связывается с новой сеткой.
На шаге 3 создается и применяется наложение текстуры:
D3DRMBOX box; meshbuilder->GetBox(&box); D3DVALUE width = box.max.x - box.min.x; D3DVALUE height = box.max.y - box.min.y; LPDIRECT3DRMWRAP wrap; d3drm->CreateWrap(D3DRMWRAP_FLAT, NULL, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), // начало координат наложения D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0), // ось z D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0), // ось y D3DVALUE(0.5), D3DVALUE(0.5), // начало координат D3DDivide(1,width),D3DDivide(1,height), // масштаб &wrap); wrap->Apply(meshbuilder); wrap->Release(); wrap = 0;Плоское наложение текстуры создается таким образом, чтобы размеры текстуры в точности соответствовали сетке. Наложение текстуры применяется к конструктору сеток с помощью функции Apply().
Теперь загруженный ранее конструктор сеток используется для инициализации указателя на интерфейс Direct3DRMMesh:
meshbuilder->CreateMesh(&mesh); meshbuilder->Release(); meshbuilder = 0;Новая сетка создается с теми же параметрами, что были у конструктора сеток.
Затем создается фрейм и сетка присоединяется к нему:
LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(mesh); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0.05)); meshframe->AddMoveCallback(UpdateTexture, NULL); meshframe->Release(); meshframe = 0;Новому фрейму назначаются такие атрибуты вращения, чтобы он поворачивался вокруг оси Y. Кроме того, для фрейма устанавливается функция обратного вызова UpdateTexture().
Функция обратного вызова UpdateTexture() отвечает за установку новой текстуры при каждом обновлении экрана. Код функции UpdateTexture() выглядит следующим образом:
void ShowRoomWin::UpdateTexture(LPDIRECT3DRMFRAME frame, void*, D3DVALUE) { static UINT count; int curtex = count % 15; frame->DeleteVisual(mesh); mesh->SetGroupTexture(0, texture[curtex]); frame->AddVisual(mesh); count++; }Статическая переменная count используется для того, чтобы программа могла определить, какая из текстур должна быть наложена на сетку. Новая текстура накладывается с помощью функции SetGroupTexture() интерфейса Direct3DRMMesh. Обратите внимание, что конструктор сеток удаляется из фрейма перед вызовом функции SetGroupTexture() и вновь добавляется к фрейму после. Это вызвано тем обстоятельством, что функция SetGroupTexture() не оказывает никакого эффекта на конструктор сеток, присоединенный к фрейму.
Функция SetGroupTexture() интерфейса Direct3DRMMesh похожа на функцию SetTexture() интерфейса Direct3DRMMeshBuilder, которую мы использовали в предыдущих примерах. Отличие заключается в том, что интерфейс Direct3DRMMesh поддерживает группы, а интерфейс Direct3DRMMeshBuilder — нет. Группой называется набор граней, которым можно управлять как единой сущностью. Более подробно об интерфейсе Direct3DRMMesh мы поговорим в главе 8.
Функция TextureDriftWin::CreateScene() создает одну сетку и одну текстуру. Текстура связывается с сеткой, но наложения текстуры не выполняется. При каждом обновлении экрана функция обратного вызова генерирует новое наложение текстуры. Текст функции CreateScene() представлен в листинге5.5.
Листинг 5.5. Функция TextureDriftWin::CreateScene() |
BOOL TextureDriftWin::CreateScene() { //------ КОНСТРУКТОР СЕТОК ------ D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_D3DMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER meshbuilder; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->Scale(D3DVALUE(1), D3DVALUE(1), D3DVALUE(.5)); ScaleMesh(meshbuilder, D3DVALUE(35)); meshbuilder->SetPerspective(TRUE); //------- ТЕКСТУРА ------ HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE( IDR_TEXTURE), "TEXTURE"); LPDIRECT3DRMTEXTURE texture; d3drm->LoadTextureFromResource(texture_id, &texture); meshbuilder->SetTexture(texture); texture->Release(); texture = 0; //-------- СЕТКА -------- LPDIRECT3DRMMESH mesh; meshbuilder->CreateMesh(&mesh); meshbuilder->Release(); meshbuilder = 0; //-------- ФРЕЙМ СЕТКИ -------- LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); meshframe->AddVisual(mesh); meshframe->AddMoveCallback(MoveTexture, NULL); meshframe->Release(); meshframe = 0; mesh->Release(); mesh = 0; //-------- СВЕТ ---------- LPDIRECT3DRMLIGHT light; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1),D3DVALUE(1), D3DVALUE(1), &light); scene->AddLight(light); light->Release(); light = 0; //-------- ПОРТ ПРОСМОТРА ---------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(-50.0)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; } |
Функция CreateScene() выполняет следующие действия:
Создание сетки с использованием интерфейса Direct3DRMMeshBuilder. Создание текстуры для сетки. Создание интерфейса Direct3DRMMesh. Создание фрейма для сетки. Создание источника света. Создание порта просмотра.Сначала для загрузки сетки из ресурсов приложения используется интерфейс Direct3DRMMeshBuilder:
D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_D3DMESH); resinfo.lpType = "MESH"; LPDIRECT3DRMMESHBUILDER meshbuilder; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); meshbuilder->Scale(D3DVALUE(1), D3DVALUE(1), D3DVALUE(.5)); ScaleMesh(meshbuilder, D3DVALUE(35)); meshbuilder->SetPerspective(TRUE);Обратите внимание, что после загрузки сетки мы используем функцию Scale() интерфейса Direct3DRMMeshBuilder для уменьшения размера сетки по оси Z. Мы передаем функции Scale() в качестве аргумента Z значение 0.5, чтобы вдвое уменьшить размер сетки по оси Z. После вызова функции Scale() используется функция ScaleMesh() для масштабирования сетки таким образом, чтобы ее максимальный размер равнялся 35 единицам. Функция SetPerspective() вызывается чтобы разрешить перспективную коррекцию.
Затем создается текстура:
HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_TEXTURE), "TEXTURE"); LPDIRECT3DRMTEXTURE texture; d3drm->LoadTextureFromResource(texture_id, &texture); meshbuilder->SetTexture(texture); texture->Release(); texture = 0;Новая текстура привязывается к ранее созданному конструктору сеток с помощью функции SetTexture(), но наложение текстуры не создается. Потом существующий конструктор сеток применяется для создания экземпляра интерфейса Direct3DRMMesh:
LPDIRECT3DRMMESH mesh; meshbuilder->CreateMesh(&mesh); meshbuilder->Release(); meshbuilder = 0;После вызова функции CreateMesh() конструктор сеток нам больше не нужен, поэтому указатель на него освобождается.
Теперь настала пора создать фрейм и присоединить к нему сетку:
LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); meshframe->AddVisual(mesh); meshframe->AddMoveCallback(MoveTexture, NULL); meshframe->Release(); meshframe = 0; mesh->Release(); mesh = 0;Новый фрейм ориентируется таким образом, чтобы сетка была расположена под углом 45 градусов к порту просмотра, после чего для присоединения сетки к фрейму вызывается функция AddVisual(). Функция обратного вызова MoveTexture(), которая будет анимировать текстуру, устанавливается с помощью функции AddMoveCallback().
Обратите внимание, что указатель mesh освобождается. Ранее в функции мы уже освободили указатели meshbuilder и texture, а значит у нас больше не осталось никаких указателей на графические объекты. Это затруднило бы установку новых параметров наложения текстуры, если бы не существовало способа получить визуальные объекты фрейма. Мы увидим как это делается при обсуждении функции обратного вызова MoveTexture().
На пятом и шестом этапах для сцены создаются источник света и порт просмотра.
MoveTexture() — это функция обратного вызова, которая при каждом вызове создает и применяет новое наложение текстуры. В самом начале функции должен быть получен интерфейс Direct3DRMMesh, который был добавлен к фрейму в функции CreateScene(). Код функции MoveTexture() показан в листинге 5.6.
Листинг 5.6. Функция TextureDriftWin::MoveTexture() |
void TextureDriftWin::MoveTexture(LPDIRECT3DRMFRAME frame, void*, D3DVALUE) { static D3DVALUE xtex; xtex += D3DVALUE(.02); LPDIRECT3DRMVISUALARRAY visualarray; frame->GetVisuals(&visualarray); int nvisuals = visualarray->GetSize(); for ( int i = 0; i < nvisuals; i++ ) { LPDIRECT3DRMVISUAL visual; visualarray->GetElement(i, &visual); LPDIRECT3DRMMESH mesh; if (visual->QueryInterface(IID_IDirect3DRMMesh, (void**)&mesh) == 0) { D3DRMBOX box; mesh->GetBox(&box); D3DVALUE w = box.max.x - box.min.x; D3DVALUE h = box.max.y - box.min.y; LPDIRECT3DRMWRAP wrap; d3drm->CreateWrap(D3DRMWRAP_FLAT, NULL, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0), xtex, D3DVALUE(0.5), // начало координат текстуры D3DDivide(1,w), D3DDivide(1,h), // масштаб текстуры &wrap); wrap->Apply(mesh); wrap->Release(); wrap = 0; mesh->Release(); mesh = 0; } visual->Release(); visual = 0; } visualarray->Release(); visualarray = 0; } |
В начале функции MoveTexture() объявляется статическая переменная типа D3DVALUE, которая увеличивается при каждом вызове функции. Значение этой переменной используется как значение координаты X для начала координат текстуры, поэтому увеличение этого значения вызывает эффект перемещения текстуры по сетке.
Затем, для получения массива присоединенных к фрейму визуальных объектов, используется функция GetVisuals() интерфейса Direct3DRMFrame (Direct3D передает указатель frame в первом параметре функции обратного вызова).
Функция GetVisuals() инициализирует указатель на интерфейс Direct3DRMVisualArray. Этот интерфейс поддерживает всего лишь две функции: GetSize() и GetElement().
Для перебора элементов массива визуальных объектов применяется цикл. С помощью функции GetSize() мы определяем количество элементов в массиве, а для получения отдельного элемента используется функция GetElement(). Возвращаемый функцией указатель является указателем на интерфейс Direct3DRMVisual (который не предоставляет никаких функций). Чтобы узнать, поддерживает ли объект интерфейс Direct3DRMMesh, используется функция QueryInterface(). Мы знаем что один и только один из объектов, возвращаемых функцией GetElement() поддерживает интерфейс Direct3DRMMesh, поскольку в функции CreateScene() мы создали и присоединили к данному фрейму только одну сетку.
После получения указателя на интерфейс Direct3DRMMesh создается и применяется к сетке интерфейс Direct3DRMWrap.
Функция ApplyWraps() отвечает за назначение каждой из сеток правильного типа покрытия текстурой в соответствии со значениями переменных класса WrapsWin. Ниже приведен код функции ApplyWraps():
void WrapsWin::ApplyWraps() { if (boxwraptype == D3DRMWRAP_FLAT) ApplyFlat(box); else if (boxwraptype == D3DRMWRAP_CYLINDER) ApplyCylinder(box); else ApplySphere(box); if (cylwraptype == D3DRMWRAP_FLAT) ApplyFlat(cyl); else if (cylwraptype == D3DRMWRAP_CYLINDER) ApplyCylinder(cyl); else ApplySphere(cyl); if (spherewraptype == D3DRMWRAP_FLAT) ApplyFlat(sphere); else if (spherewraptype == D3DRMWRAP_CYLINDER) ApplyCylinder(sphere); else ApplySphere(sphere); }Функция ApplyWraps() определяет используемый для каждой из сеток способ наложения текстуры и вызывает необходимую функцию, чтобы применить соответствующее наложение текстуры. Поскольку все эти функции практически идентичны, мы приведем только текст функции ApplyFlat():
void WrapsWin::ApplyFlat(LPDIRECT3DRMMESHBUILDER meshbuilder) { D3DRMBOX box; meshbuilder->GetBox(&box); D3DVALUE width = box.max.x - box.min.x; D3DVALUE height = box.max.y - box.min.y; LPDIRECT3DRMWRAP wrap; d3drm->CreateWrap(D3DRMWRAP_FLAT, NULL, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(0.0), // начало координат наложения D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(1.0), // ось z D3DVALUE(0.0), D3DVALUE(1.0), D3DVALUE(0.0), // ось y D3DVALUE(0.5), D3DVALUE(0.5), // начало координат текстуры D3DDivide(1,width), D3DDivide(1,height), // масштаб текстуры &wrap); wrap->Apply(meshbuilder); wrap->Release(); wrap = 0; }Сначала функция ApplyFlat() вычисляет высоту и ширину сетки, на которую наносится текстура. Функция GetBox() интерфейса Direct3DRMMeshBuilder используется для инициализации структуры D3DRMBOX размерами сетки. Затем с помощью функции CreateWrap() интерфейса Direct3DRM создается экземпляр интерфейса Direct3DRMWrap. Новое наложение применяется к сетке функцией Apply() интерфейса Direct3DRMWrap. Перед завершением функции выполняется освобождение указателя wrap.
В классе WrapsWin есть еще три функции: OnWrapsFlat(), OnWrapsCylinder() и OnWrapsSphere(). Эти функции являются обработчиками событий для меню Wraps и выглядят аналогично функции OnWrapsReset(), за исключением того, что каждая из них назначает одинаковый тип наложения текстуры для всех трех сеток. Ниже для примера приведен текст функции OnWrapsFlat():
void WrapsWin::OnWrapsFlat() { boxwraptype = D3DRMWRAP_FLAT; cylwraptype = D3DRMWRAP_FLAT; spherewraptype = D3DRMWRAP_FLAT; ApplyWraps(); }Давайте взглянем на код приложения Wraps. Как и все приложения в этой книге, данное приложение использует в качестве базового класс RMWin и добавляет функциональные возможности переопределяя функцию CreateScene(). Приложение Wraps объявляет класс WrapsWin функция CreateScene() которого отвечает за загрузку трех используемых в приложении сеток. Поскольку это приложение несколько сложнее, чем те, которые были рассмотрены нами ранее, часть работы CreateScene() выполняют вспомогательные функции. Ниже приведен код функции CreateScene() для приложения Wraps.
BOOL WrapsWin::CreateScene() { //-------- СЕТКИ И ТЕКСТУРЫ -------- if (LoadMeshes() == FALSE) return FALSE; if (LoadWrapsTexture() == FALSE) return FALSE; OnWrapsReset(); //--------- СВЕТ ---------- LPDIRECT3DRMFRAME lightframe; LPDIRECT3DRMLIGHT light; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(1),D3DVALUE(1), D3DVALUE(1), &light); d3drm->CreateFrame(scene, &lightframe); lightframe->AddLight(light); lightframe->Release(); lightframe = 0; light->Release(); light = 0; //---------- КАМЕРА ------------ d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0.0), D3DVALUE(0.0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; }Функция CreateScene() выполняет следующие действия:
Создание и конфигурирование сеток, текстур и наложений текстур. Создание источника света и настройка его параметров. Создание и размещение порта просмотра.Для создания и инициализации сеток, текстур и наложений текстур функция CreateScene() вызывает функции LoadMeshes(), LoadWrapsTexture() и OnWrapsReset(). Как явствует из названия, функция LoadMeshes() загружает три используемые приложением сетки. Кроме того, LoadMeshes() отвечает за создание и размещение фреймов, к которым будут присоединены эти сетки. Функция LoadWrapsTexture() загружает текстуру, которая будет наложена на каждую из трех сеток.
Функция OnWrapsReset() выполняет две задачи. Она применяется в функции CreateScene() для инициализации параметров программы и, кроме того, она же является обработчиком события, вызываемым каждый раз, когда пользователь выбирает пункт Reset в меню Wraps.
Функции LoadMeshes(), LoadWrapsTexture() и OnWrapsReset() выполняют для приложения Wraps значительный объем работы и заслуживают более пристального рассмотрения.
Функция LoadMeshes() создает три сетки и присоединяет каждую из них к отдельному фрейму. Фреймы располагаются на расстоянии друг от друга и им назначаются одинаковые атрибуты вращения. Текст функции LoadMeshes() приведен в листинге5.2.
Листинг 5.2. Функция WrapsWin::LoadMeshes() |
BOOL WrapsWin::LoadMeshes() { HRESULT r; const D3DVALUE meshscale = D3DVALUE(13); const D3DVALUE meshspacing = D3DVALUE(15); D3DRMLOADRESOURCE resinfo; resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_BOXMESH); resinfo.lpType = "MESH" d3drm->CreateMeshBuilder(&box); r = box->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); if (r != D3DRM_OK) { TRACE("failed to load internal box mesh\n"); return FALSE; } box->SetPerspective(TRUE); ScaleMesh(box, meshscale - D3DVALUE(2)); LPDIRECT3DRMFRAME boxframe; d3drm->CreateFrame(scene, &boxframe); boxframe->SetPosition(scene, -meshspacing, D3DVALUE(0), D3DVALUE(0)); boxframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(1), D3DVALUE(.1)); boxframe->AddVisual(box); resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_CYLMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&cyl); cyl->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); if (r != D3DRM_OK) { TRACE("failed to load internal cylinder mesh\n"); return FALSE; } cyl->SetPerspective(TRUE); ScaleMesh(cyl, meshscale); LPDIRECT3DRMFRAME cylframe; d3drm->CreateFrame(scene, &cylframe); cylframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(1), D3DVALUE(.1)); cylframe->AddVisual(cyl); resinfo.hModule = NULL; resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&sphere); sphere->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE, NULL, NULL); if (r != D3DRM_OK) { TRACE("failed to load internal sphere mesh\n"); return FALSE; } sphere->SetPerspective(TRUE); ScaleMesh(sphere, meshscale); LPDIRECT3DRMFRAME sphereframe; d3drm->CreateFrame(scene, &sphereframe); sphereframe->SetPosition(scene, D3DVALUE(meshspacing), D3DVALUE(0), D3DVALUE(0)); sphereframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(1), D3DVALUE(.1)); sphereframe->AddVisual(sphere); return TRUE; } |
В начале функции объявляется несколько констант, используемых в коде для позиционирования и масштабирования сеток. Затем осуществляется инициализация указателя на интерфейс Direct3DRMMeshBuilder. Функция Load() загружает изображающую куб сетку из ресурсов программы.
Затем для того чтобы разрешить перспективную коррекцию применяется функция SetPerspective(). Если вы установили демонстрационные приложения на свой компьютер, можете закомментировать вызов этой функции и заново скомпилировать приложение Wraps. Перспективная коррекция особенно заметна на сетке куба из-за его больших граней.
Затем выполняется масштабирование сетки с помощью функции ScaleMesh(). Мы уже обсуждали функцию ScaleMesh() в главе 4. ScaleMesh() определена в классе RMWin и предоставляет удобный способ для масштабирования сеток таким образом, чтобы они соответствовали заданному размеру. Константа, используемая для масштабирования куба, слегка изменяется, поскольку сетки кубической формы кажутся больше, чем другие сетки (поскольку куб занимает максимальный объем в минимальном пространстве).
После того, как масштабирование сетки выполнено, функция LoadMeshes() создает фрейм и позиционирует его в соответствии с константой meshspacing. Сетка куба появляется слева от других сеток, поэтому для нее указывается отрицательная координата по оси X. Далее функция SetRotation() назначает фрейму атрибуты вращения. И, в самом конце, с помощью функции AddVisual() интерфейса Direct3DRMFrame к фрейму присоединяется сетка.
Функция LoadWrapsTexture() загружает текстуру, изображенную на рис. 5.6 и связывает ее с тремя сетками, загруженными ранее функцией LoadMeshes(). Код функции приведен ниже:
BOOL WrapsWin::LoadWrapsTexture() { HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE( IDR_JADETEXTURE), "TEXTURE"); LPDIRECT3DRMTEXTURE texture; d3drm->LoadTextureFromResource(texture_id, &texture); box->SetTexture(texture); cyl->SetTexture(texture); sphere->SetTexture(texture); texture->Release(); texture = 0; return TRUE; }Для инициализации экземпляра структуры HRSRC в функции LoadWrapsTexture() применяется функция FindResource() из Win32 API. Структура HRSRC идентифицирует текстуру, которую мы пытаемся загрузить, и используется в качестве аргумента функции LoadTextureFromResource(). Новая текстура связывается с тремя сетками с помощью функции SetTexture() интерфейса Direct3DRMMeshBuilder. В конце функции освобождается указатель на текстуру и возвращается значение TRUE, сообщающее об успешном завершении.
Вспомните, что перед созданием источника света и порта просмотра функция CreateScene() приложения Wraps вызывает три функции: LoadMeshes(), LoadWrapsTexture() и OnWrapsReset(). Функция OnWrapsReset() присваивает значения переменным класса WrapsWin таким образом, чтобы для сетки куба использовалось плоское наложение текстуры, для сетки цнлиндра— цилиндрическое, и для сетки сферы — сферическое наложение текстуры. Вот как выглядит код функции OnWrapsReset():
void WrapsWin::OnWrapsReset() { boxwraptype = D3DRMWRAP_FLAT; cylwraptype = D3DRMWRAP_CYLINDER; spherewraptype = D3DRMWRAP_SPHERE; ApplyWraps(); }Переменные boxwraptype, cylwraptype и spherewraptype принадлежат к классу WrapsWin и относятся к объявленному в Direct3D типу D3DRMWRAPTYPE. Функция OnWrapReset() присваивает каждой из этих переменных константы, которые также предоставляются Direct3D. В следующем разделе мы обсудим функцию ApplyWraps().
Эта глава первая из шести, описывающих специфические технологии Direct3D. Каждая технология обсуждается максимально подробно и сопровождается демонстрационным примером. Для самых нетерпеливых (таких, как я), перечислю демонстрационные программы, рассматриваемые в этой главе:
Jade Wraps Decal OrbStar TextureDrift ShowRoomДемонстрационные примеры для этой главы, также как и все примеры на CD-ROM, были созданы с помощью Direct3D AppWizard, и поэтому имеют одинаковую общую структуру. Это позволяет нам обсуждать только те части, которые являются уникальными для данного примера. Общий обзор структуры программ, созданных с помощью AppWizard приведен в главе 4.
Основная функциональность приложения Decal сосредоточена в классе DecalWin:
class DecalWin : public RMWin { public: BOOL CreateScene(); protected: //{{AFX_MSG(DecalWin) //}}AFX_MSG DECLARE_MESSAGE_MAP() };Класс DecalWin очень простой. В нем объявлена только одна функция: CreateScene(). Код класса после слова protected содержит код мастера ClassWizard, необходимый если в дальнейшем будут добавляться обработчики событий.
Большинство функциональных возможностей приложения Jade реализовано в классе JadeWin. Класс JadeWin наследует базовую функциональность Direct3D от класса RMWin, который был подробно описан в главе4. Определение класса JadeWin выглядит следующим образом:
class JadeWin : public RMWin { public: JadeWin(); BOOL CreateScene(); static void MoveFrame(LPDIRECT3DRMFRAME frame, void*, D3DVALUE); protected: //{{AFX_MSG(JadeWin) afx_msg void OnRenderWireframe(); afx_msg void OnRenderFlat(); afx_msg void OnRenderGouraud(); afx_msg void OnUpdateRenderFlat(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderGouraud(CCmdUI* pCmdUI); afx_msg void OnUpdateRenderWireframe(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: LPDIRECT3DRMMESHBUILDER meshbuilder; };Здесь объявлены три открытые функции: конструктор, функция CreateScene() и функция обратного вызова с именем MoveFrame(). Конструктор применяется для инициализации единственной переменной класса:
JadeWin::JadeWin() { meshbuilder=0; }Функция CreateScene() создает используемые в сцене объекты и настраивает их параметры. Функция MoveFrame() объявлена как статическая, чтобы функция CreateScene() могла установить ее в качестве функции обратного вызова. MoveFrame() используется для изменения ориентации фрейма к которому в демонстрационном приложении присоединена сетка. Это заставляет сетку менять ориентацию при каждом обновлении экрана.
Шесть защищенных функций являются обработчиками событий и предназначены для поддержки функционирования меню Render приложения Jade.
Класс OrbStarWin предоставляет большинство функциональных возможностей приложения OrbStar. Определение класса выглядит следующим образом:
class OrbStarWin : public RMWin { public: BOOL CreateScene(); static void MoveSphere(LPDIRECT3DRMFRAME frame, void* arg, D3DVALUE delta); static void MoveStar(LPDIRECT3DRMFRAME frame, void* arg, D3DVALUE delta); protected: //{{AFX_MSG(OrbStarWin) //}}AFX_MSG DECLARE_MESSAGE_MAP() };В классе объявлены три функции: CreateScene(), MoveSphere() и MoveStar(). Функция CreateScene() конструирует для приложения сцену. MoveSphere() и MoveStar() это функции обратного вызова, которые будут использоваться для анимации двух сеток в сцене.
Функциональность приложения ShowRoom сосредоточена в классе ShowRoomWin. Определение этого класса выглядит следующим образом:
class ShowRoomWin : public RMWin { public: BOOL CreateScene(); static void UpdateTexture(LPDIRECT3DRMFRAME frame, void*, D3DVALUE); protected: //{{AFX_MSG(ShowRoomWin) //}}AFX_MSG DECLARE_MESSAGE_MAP() private: static LPDIRECT3DRMMESH mesh; static LPDIRECT3DRMTEXTURE texture[15]; };В классе объявлены две открытые функции: CreateScene() и UpdateTexture(). Функция CreateScene() используется для создания сцены, а UpdateTexture() — это функция обратного вызова, применяемая для изменения накладываемой в данный момент на сетку текстуры.
Также объявлены две закрытых переменных: mesh и texture. Переменная mesh это указатель на интерфейс Direct3DRMMesh , используемый для доступа к сетке, на которую накладывается анимируемая текстура. Переменная texture является массивом текстур, которые будут по очереди накладываться на сетку.
Все возможности приложения TextureDrift реализованы в классе TextureDriftWin. Определение класса выглядит следующим образом:
class TextureDriftWin : public RMWin { public: BOOL CreateScene(); static void MoveTexture(LPDIRECT3DRMFRAME frame, void* arg, D3DVALUE delta); protected: //{{AFX_MSG(TextureDriftWin) //}}AFX_MSG DECLARE_MESSAGE_MAP() };В классе объявлены две функции: CreateScene() и MoveTexture(). Функция CreateScene() создает сцену для приложения, а функция MoveTexture() является функцией обратного вызова, используемой для выполнения анимации текстуры.
Часть кода после ключевого слова protected будет использоваться, если в дальнейшем вы захотите добавить к приложению обработчики событий с помощью мастера ClassWizard.
Большая часть функциональных возможностей приложения Wraps реализуется классом WrapsWin. Давайте взглянем на определение этого класса:
class WrapsWin : public RMWin { public: WrapsWin(); BOOL CreateScene(); protected: //{{AFX_MSG(WrapsWin) afx_msg void OnWrapsFlat(); afx_msg void OnWrapsCylinder(); afx_msg void OnWrapsSphere(); afx_msg void OnWrapsReset(); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: BOOL LoadMeshes(); BOOL LoadTexture(); void ApplyWraps(); void ApplyFlat(LPDIRECT3DRMMESHBUILDER); void ApplyCylinder(LPDIRECT3DRMMESHBUILDER); void ApplySphere(LPDIRECT3DRMMESHBUILDER); private: LPDIRECT3DRMMESHBUILDER box; LPDIRECT3DRMMESHBUILDER cyl; LPDIRECT3DRMMESHBUILDER sphere; D3DRMWRAPTYPE boxwraptype; D3DRMWRAPTYPE cylwraptype; D3DRMWRAPTYPE spherewraptype; };Также как и класс JadeWin, класс WrapsWin предоставляет две открытые функции: конструктор и функцию CreateScene(). Конструктор используется для инициализации переменных класса. Функция CreateScene() создает сцену приложения. Чуть позже функция CreateScene() будет разобрана более подробно.
В приложении Wraps отсутствует меню Render, но есть меню Wraps. Меню Wraps позволяет пользователю выбрать используемый метод наложения текстуры. Для поддержки функциональных возможностей меню Wraps в классе WrapsWin объявлены четыре защищенные функции: OnWrapsFlat(), OnWrapsCylinder(), OnWrapsSphere() и OnWrapsReset().
Кроме того, в классе объявлены шесть закрытых функций. Функции LoadMeshes() и LoadTexture() применяются для упрощения функции CreateScene(). Оставшиеся четыре функции применяются для изменения параметров наложения текстуры во время работы приложения.
Также объявлены шесть закрытых переменных. Переменные box, cyl и sphere являются указателями на интерфейс Direct3DRMMeshBuilder. Они применяются для доступа к трем сеткам, образующим сцену. Оставшиеся три переменные используются для того, чтобы определить какой тип наложения текстуры используется для каждой из сеток.
Приложение Jade использует плоское наложение текстуры. Direct3D поддерживает еще два способа наложения текстуры: цилиндрический и сферический. Для цилиндрического покрытия текстура изгибается в одном направлении так, чтобы соединились ее противоположные края. Для сферического покрытия текстура деформируется так, чтобы из нее была образована сфера. Как мы вскоре увидим— данные методы покрытия всего лишь аппроксимации.
Прежде чем начать рассматривать код, давайте поговорим о стоящей перед нами цели. Мы собираемся помещать текстуры на объекты. Одним из возможных способов является наложение текстуры. Наложение текстуры — это метод, определяющий, как текстура соединяется с сеткой. Direct3D поддерживает три способа наложения текстур: плоский, цилиндрический и сферический.
Одним из способов поэкспериментировать с наложением текстур является использование программы Xpose, утилиты для просмотра X-файлов, расположенной на CD-ROM. Вы можете применять Xpose для вращения сетки, изменения ее параметров, и сохранения сетки после всех преобразований. Окно программы Xpose показано на рис. 5.1.
Рис. 5.1. Программа Xpose
Xpose позволяет наложить текстуру на сетку. Для наложения новой текстуры выберите команду Load из меню Texture. На экран будет выведено диалоговое окно Open File. В этом окне вы можете выбрать файл BMP или PPM, но размеры текстуры должны быть степенью двойки (16, 32, 64, 128...).
По умолчанию Xpose использует сферическое наложение текстур, но этот параметр можно изменить в диалоговом окне Texture Wrap Settings. Для доступа к этому диалоговому окну выберите команду Wrap Settings в меню Texture. Вид диалогового окна Texture Wrap Settings представлен на рис. 5.2.
Рис. 5.2. Диалоговое окно Texture Wrap Settings
Вы можете изменить любые параметры наложения текстуры, сделав требуемые изменения в этом диалоговом окне и щелкнув по кнопке OK.
Приложение Xpose весьма полезно и для экспериментов с другими возможностями Direct3D.
Первое, что нам следует сделать— наложить текстуру на объект. Наша стратегия состоит в следующем: возьмем демонстрационную программу, аналогичную проекту, который создает по умолчанию мастер Direct3D AppWizard, и наложим текстуру на объект, отображаемый демонстрационным приложением.
Поддержка декалов в Direct3D представлена в приложении Decal. Приложение Decal анимирует два декала, перемещая их вокруг сцены. Окно приложения Decal показано на рис. 5.7.
Рис. 5.7. Приложение Decal
Приложение Decal демонстрирует следующие технологии:
Поддержка декалов в Direct3D. Использование пустых фреймов для анимации объектов.Перед тем, как перейти к рассмотрению кода приложения Decal, следует поговорить о пустых фреймах. Пустой фрейм (dummy frame) — это фрейм, используемый только для анимации других фреймов. К пустому фрейму не присоединено никаких видимых объектов, таких как сетки или декалы, и он используется как родительский фрейм для других фреймов к которым присоединены видимые объекты.
В приложении Decal выполняется анимация двух декалов, перемещающихся по орбите вокруг начала координат. Это осуществляется путем помещения в начало координат двух пустых фреймов и назначения каждому из них своих параметров вращения. Декалы присоединяются к двум другим (не пустым) фреймам. Каждый из этих фреймов с декалами является дочерним для одного из пустых фреймов и размещается на некотором расстоянии от начала координат. Поскольку дочерние фреймы имитируют движение родителя, фреймы с декалами будут перемещаться по орбите вокруг своих родительских фреймов.
Приложение OrbStar использует прозрачность текстуры при анимации звезды, помещенной внутри сферы. И сетка звезды, и сетка сферы расположены в начале координат. Сфера масштабируется таким образом, чтобы она была больше звезды, поэтому если наложенная на сферу текстура не имеет прозрачных участков, звезда не видна зрителю. Окно приложения OrbStar показано на рис.5.8.
Рис. 5.8. Приложение OrbStar
Текстура, которая будет наложена на сферу в данном приложении, изображена на рис. 5.9.
Рис. 5.9. Текстура, используемая в приложении OrbStar
Приложение OrbStar демонстрирует следующие технологии:
Использование прозрачных текстур. Использование нескольких функций обратного вызова. Использование функции D3DRMVectorRandom() для генерации случайного вектора.В приложении ShowRoom анимация текстуры выполняется путем последовательного наложения на одну и ту же сетку нескольких текстур из массива. Используемая в приложении ShowRoom сетка представляет собой простой куб. На используемых текстурах представлено изображение автомобиля с разных направлений. При каждом обновлении экрана используется новая текстура, поэтому зритель видит анимированное изображение автомобиля. Сетка, на которую накладываются текстуры, вращается, так что анимированы и сетка, и текстура. Вид окна приложения ShowRoom показан на рис.5.11.
Рис. 5.11. Приложение ShowRoom
В приложении ShowRoom демонстрируется применение следующих технологий:
Выполнение анимации текстуры с использованием нескольких текстур. Использование интерфейса Direct3DRMMesh. Анимация с использованием атрибутов вращения.Подобно приложению TextureDrift, приложение ShowRoom использует интерфейс Direct3DRMMesh, чтобы избежать снижения быстродействия, вызванного выполняемой интерфейсом Direct3DRMMeshBuilder лишней работой.
В приложении TextureDrift используется одна сетка и одна текстура. При каждом обновлении экрана текстура заново накладывается на сетку со слегка измененным значением начала координат, что вызывает эффект скольжения текстуры по сетке. Окно приложения TextureDrift показано на рис. 5.10 (чтобы увидеть анимацию текстуры, вам необходимо запустить приложение TextureDrift на своем компьютере).
Рис. 5.10. Приложение TextureDrift
Приложение TextureDrift демонстрирует следующие технологии:
Анимация отдельной текстуры путем изменения параметров наложения текстуры на сетку. Применение интерфейса Direct3DRMMesh чтобы увеличить быстродействие, благодаря отказу от дополнительной работы, выполняемой интерфейсом Direct3DRMMeshBuilder. Использование функции GetVisuals() интерфейса Direct3DRMFrame для получения указателя на присоединенный к фрейму объект.В приложении TextureDrift используется техника, которая слегка отличается от той, которая применялась в рассмотренных ранее приложениях. Ранее для создания и отображения сеток использовался интерфейс Direct3DRMMeshBuilder (за исключением приложения Decal в котором вообще не использовались сетки). В приложении TextureDrift интерфейс Direct3DRMMeshBuilder применяется для создания сетки, но не для ее отображения. Вместо этого интерфейс Direct3DRMMeshBuilder используется для создания экземпляра интерфейса Direct3DRMMesh. Это делается из-за соображений быстродействия. В главе 3 говорилось, что всякий раз, когда выполняется модификация параметров сетки, экземпляр интерфейса Direct3DRMMeshBuilder создает внутри себя экземпляр интерфейса Direct3DRMMesh. Чтобы приложение TextureDrift не выполняло лишней работы, мы используем интерфейс Direct3DRMMesh непосредственно.
Это не означает, что предыдущие приложения были плохо спроектированы. Если во время работы приложения не выполняется частой модификации параметров сеток, интерфейс Direct3DRMMeshBuilder обеспечивает хорошее быстродействие. В приложении TextureDrift мы применили интерфейс Direct3DRMMesh из-за того, что характеристики сетки меняются при каждом обновлении экрана.
Приложение Wraps отображает три сетки: куб, цилиндр и сферу. На каждую сетку накладывается единственная текстура, но метод наложения текстуры можно изменять. По умолчанию для куба используется плоское наложение, для цилиндра — цилиндрическое и для сферы — сферическое. Чтобы изменять тип наложения текстуры на сетку, приложение предоставляет соответствующие команды меню. Окно приложения Wraps показано на рис. 5.5.
Рис. 5.5. Приложение Wraps
В приложении Wraps используется слегка модифицированная версия текстуры из приложения Jade. Линии, нарисованные поперек текстуры, делают видимой деформацию, происходящую при наложении текстуры. Измененная текстура показана на рис. 5.6.
Рис. 5.6. Текстура, используемая в приложении Wraps
Приложение Wraps демонстрирует следующие технологии:
Загрузка и отображение нескольких сеток. Применение произвольного способа наложения текстуры на сетку во время выполнения программы. Меню Wraps позволяет изменить метод наложения текстуры на объект во время работы программы.Интерфейс Direct3DRMTexture позволяет вам указать цвет текстуры, который будет восприниматься Direct3D как прозрачный. Сквозь прозрачные участки текстуры можно видеть сетки и текстуры, которые в ином случае были бы скрыты от глаз зрителя.
По умолчанию прозрачность текстур отключена. Для разрешения и запрещения прозрачности текстур может применяться функция SetDecalTransparency() интерфейса Direct3DRMTexture. После разрешения прозрачности любые черные точки текстуры будут считаться прозрачными. Цвет, который считается прозрачным, можно изменить с помощью функции SetDecalTransparencyColor().