Наше первое трехмерное
Глава 1
Наше первое трехмерное
приложение
Рисунок. 1-1. Приложение Basic
Глава 1. Наше первое трехмерное приложение
Не ахти какое достижение, но вы вскоре убедитесь, что приложение Basic устроено достаточно просто, а это немаловажно. В библиотеках MFC, DirectX и 3dPlus спрятано довольно много кода, однако для построения нашего первого приложения совсем не обязательно знать, как он работает. Вам необходимо лишь чуть-чуть помочь в самом начале. Дальше остается только выбирать функции, которые нужно вызывать для достижения желаемого эффекта. В оставшейся части книги мы рассмотрим структуру библиотеки 3dPlus и разберем код многих программ. Итак, попрощайтесь со своей собакой, сделайте глубокий вдох и прыгайте в море — посмотрим, умеете ли вы плавать.
Построение приложения с самого начала
В мире встречаются две категории программистов. Как мне кажется, соотношение между ними составляет примерно 50:50. Первые берут существующую программу и «рихтуют» ее до тех пор, пока она не станет делать то, что требуется. Представители второй категории предпочитают начинать работу «с пустого места» и делать все собственными руками. Я принадлежу к числу последних. Не люблю пользоваться чьим-нибудь кодом, потому что могу упустить какие-нибудь тонкости в его работе; а значит, дальнейшая отладка превратится в сущий кошмар. На тот случай, если вы не захотите взять готовую программу Basic и повозиться с ней, я опишу процесс ее построения (разумеется, пока вам придется пользоваться моей библиотекой 3dPlus, но к концу книги вы сможете написать свой вариант такой библиотеки). Если вас совершенно не интересует, как создавалось это приложение, пропустите данный раздел. Вы всегда сможете вернуться к нему в случае необходимости.
Для построения приложения Basic следует выполнить следующие действия:
1. С помощью Visual C++ MFC AppWizard (EXE) создайте однодокумент-ное (SDI — Single Document Interface) приложение без поддержки баз данных и OLE. Можете также убрать из него панель инструментов, строку состояния, печать и объемные элементы управления. Получится простейшее приложение для Windows. Мой проект назывался Basic. Я выбрал вариант для работы с MFC в качестве совместно используемой библиотеки DLL (As A Shared DLL), но при желании можно осуществить статическую компоновку.
2. Исключите из проекта файлы для классов вида и документа. В моем случае эти файлы назывались BasicDoc.h, BasicDoc.cpp, BasicView.h и BasicView.cpp. Файлы следует удалить как из проекта, так и из рабочего каталога.
3. Аналогичным образом удалите файлы главного окна (обычно они называются MainFrm.h и MainFrm.cpp). Остаются два файла на C++: Basic.cpp и StdAfe.cpp.
4. Отредактируйте исходные файлы и уберите из них любые ссылки на заголовочные файлы классов документа, вида или главного окна. Обычно в этот момент я также удаляю некоторые ресурсы (скажем, окно диалога About), ненужные меню, строковые таблицы, однако большую часть этой работы можно проделать и позднее.
5. В файл StdAfx.h добавьте директивы для включения файлов mmsystem.h и d3drmwin.h. Заголовочный файл mmsystem используется в функциях для работы с джойстиком, которые понадобятся нам позднее, а в файле d3drmwin определяются все функции Direct3D.
'l&lfc Построение приложения с самого начала ЩЦ 19
BOOL CBasicApp::Initlnstance() {
// Создать главное окно C3dWnd* pWnd = new C3dWnd;
pWnd->Create("3D Basics",
WS_OVERLAPPEDWINDOW ¦ WS_VISIBLE,
50, 50,
400, 350);
m pMainWnd = pWnd;
pWnd->UpdateWindow() ;
// Создать исходный макет, к которому // будут присоединяться объекты static C3dScene scene;
scene.Create () ;
// Установить интенсивность рассеянного света scene.SetAmbientLight(0.4, 0.4, 0.4);
// Добавить направленный источник света для создания бликов static C3dDirLight dl;
dl.Create(0.8, 0.6, 0.8);
scene.AddChild(Sdl) ;
dl.SetPosition(-2, 2, -5);
dl.SetDirectionfl, -1, 1);
// Создать большую белую сферу static C3dShape shi;
shi.CreateSphere (1) ;
// Присоединить к макету большую белую сферу scene.AddChild(Sshl) ;
// Создать маленькую синюю сферу static C3dShape sh2;
sh2.CreateSphere(0.3) ;
sh2.SetColor(0, 0, 1);
// Присоединить синюю сферу к белой shl.AddChild(&sh2) ;
// Задать положение синей сферы // по отношению к белой sh2.SetPosition (О, О, -2);
// Создать маленькую красную сферу static C3dShape sh3;
sh3.CreateSphere (0.15) ;
sh3.SetColor(l, О, О);
«^ Программа ^Ц 21
// Присоединить красную сферу к белой shl.AddChild(&sh3) ;
// Задать положение красной сферы // по отношению к белой sh3.SetPosition(0, 0, 5) ;
// Начать медленно вращать белую сферу // вокруг оси 1, 1, О shl.SetRotation(l, I, 0, 0.015);
// Присоединить весь макет к сцене pWnd->SetScene(Sscene) ;
return TRUE;
BOOL CBasicApp::OnIdle(LONG ICount) {
BOOL bMore = CWinApp::Onldle(ICount);
if (m pMainWnd) (
CSdWnd* pWnd = (CSdWnd*) m_pMainWnd;
// Приказать трехмерному окну сместить макет
//на одну единицу
//и перерисовать окно
if (pWnd->Update(l)) ( ЬМоге = TRUE;
return bMore;
} ) return bMore;
Функция CBasicApp::lnitlnstance создает окно и те объекты, которые образуют макет, а функция CBasicApp::Onldle обновляет положение этих объектов во время пассивной (idle) работы приложения. Класс CBasicApp был сгенерирован AppWizard во время создания приложения. Он является производным от класса CWinApp, входящего в MFC и обеспечивающего основную функциональность Windows-приложения. Давайте подробно рассмотрим, что же делают эти две функции.
Первое, что происходит в Initlnstance, — построение окна, в котором будет отображаться трехмерный макет. Окно класса CWinApp создается в качестве главного окна приложения. Класс C3dWnd принадлежит библиотеке 3dPlus, как и все остальные рассматриваемые нами классы, имена которых начинаются с префикса C3d (исходный текст библиотеки 3dPlus находится на прилагаемом диске CD-ROM вместе с другими примерами). Указатель на созданное окно присваивается переменной m_pMainWnd, являющейся членом базового класса CWinApp из MFC. Указатель на окно используется кодом MFC при обработке сообщений приложением и т. д. Завершающим шагом в создании окна является вызов функции UpdateWindow для прорисовки окна на экране.
22 Глава 1. Каше первое трехмерное приложение
Затем создается объект класса CSdScene. В него входят все элементы одной сцены, отображаемой в трехмерном окне (например, источники света, трехмерные объекты и т. д.).
Следующим шагом является задание источников света. Мы пользуемся двумя различными источниками света: рассеянным и направленным. Рассеянный свет равномерно освещает все объекты. Если не пользоваться другим освещением, при отсутствии бликов объекты выглядят плоскими. Направленный свет действует по принципу прожектора. Сам по себе он дает очень резкий контраст и не позволяет рассмотреть затемненные части объектов. Когда объект освещается направленным источником света, интенсивность цвета поверхности меняется так, чтобы имитировать яркий луч, падающий из одного направления. Используя оба источника света, мы получаем приличную имитацию объемности и можем рассмотреть все участки объектов, даже если они находятся в тени по отношению к направленному свету. В классе C3dScene имеется встроенный источник рассеянного света, так что нам остается лишь задать его интенсивность. Сцену (stage) можно упрощенно рассматривать как окно приложения, в котором воспроизводится текущий макет. Интенсивность рассеянного света задается следующим образом:
scene.SetAmbientLight(O.4, 0.4, 0.4);
Свет состоит из различных оттенков красного, зеленого и синего цветов. В данном случае интенсивности красной, зеленой и синей составляющих выбираются так, чтобы образованный ими свет был белым и не искажал цветов объектов. Интенсивность цветовых составляющих может меняться от нуля до единицы (от 0,0 до 1,0). В программировании для Windows обычно применяются целые значения цветовых составляющих, лежащие в диапазоне от 0 до 255. Использование значений с плавающей точкой (double) может показаться нерациональным, однако следует помнить, что данные значения используются для математического определения цветов граней объектов, входящих в макет. Для этого приходится производить многочисленные тригонометрические и иные вычисления, так что на самом деле использование значений с плавающей точкой выглядит вполне оправданным. Конечно же, страстные поклонники C++ могут самостоятельно определить класс для цвета и переделать некоторые функции библиотеки 3dPlus, чтобы они в качестве аргумента принимали цветовой объект вместо набора отдельных составляющих. Я не стал этого делать лишь для того, чтобы в некоторых важных случаях можно было сразу увидеть значения RGB-компонент.
В отличие от рассеянного света, который требуется лишь настроить, источник направленного света необходимо сначала создать и присоединить к макету, после чего можно будет задавать его позицию и направление. Позиция характеризуется координатами по осям х, у и z. Чтобы задать ориентацию луча, мы создаем вектор, направление которого соответствует направлению светового потока. Пока вам не стоит беспокоиться о координатах и векторах; просто считайте, что свет падает с левой верхней точки сцены.
ПРИМЕЧАНИЕ
Я расположил направленный источник света наверху слева, потому что такое направление используется в объемных элементах управления (кнопках) Windows.
Программа
нет сообщений, подлежащих обработке, и при этом процессор не занят другими работающими приложениями (что на самом деле происходит большую часть времени). Мы используем период пассивности для того, чтобы переместить макет к следующему положению и перерисовать его в окне. Все это происходит при вызове функции C3dWnd::Update (pWnd->Update(1)). Аргумент функции C3dWnd::Update определяет, насколько необходимо сместить макет. Этот аргумент будет использован нами позднее, чтобы обеспечить постоянную скорость перемещения макета даже при изменяющемся периоде пассивности. А пока мы пользуемся принятым по умолчанию значением 1, чтобы макет перемещался на одну (выбираемую достаточно произвольно) единицу.
Как видите, мы не стали писать большую программу, а скорее прошлись по некоторым основным положениям. Вы заслужили особой похвалы, если обратили внимание на несколько статических переменных, которыми я воспользовался. Я ввел их для того, чтобы программа вышла как можно короче. По мере ее усовершенствования мы сделаем так, что статические переменные станут не нужны.
Рискуя повториться, я все же напомню: не стоит волноваться, даже если не все было понятно с первого раза. В дальнейшем мы еще не раз вернемся к тексту этой программы. А пока давайте рассмотрим некоторые важные моменты.
Быстродействие, тени, фреймы и координаты
Если запустить приложение Basic на приличной машине с хорошей видеокартой, выполняющей аппаратную пересылку битовых блоков (бит-блит), на вас наверняка произведет впечатление скорость его работы. Под «приличной» машиной я имею в виду как минимум компьютер 486 от 50 МГц и выше, а лучше — более современный Pentium с PCI-видеокартой. Именно видеокарта оказывается наиболее важным элементом. Если ваша видеокарта обладает «лишней» видеопамятью (то есть объем видеопамяти превышает тот, который необходим для текущего разрешения экрана) и аппаратными средствами для перемещения блоков видеопамяти, то библиотеки DirectX могут извлечь выгоду из такого положения вещей. При этом достигается заметное повышение скорости по сравнению со старыми видеокартами, в которых для перемещения данных в видеопамяти используется системный процессор. В сущности, именно поддержка аппаратных особенностей видеокарт последнего поколения и составляет одну из самых сильных сторон интерфейса Direct3D — благодаря этому вы добиваетесь от своего компьютера максимальной производительности.
Однако вернемся к нашему примеру. Каждая сфера состоит из 256 граней, которые необходимо нарисовать на экране. Для каждой грани требуется вычислить положение трех или четырех точек. Цвет грани должен изменяться в зависимости от ее положения и суммарного воздействия всех источников света, входящих в макет. Все это сопряжено с немалым объемом вычислений, и плавное движение, которое вы видите на экране, обусловлено тем, что эти вычисления происходят с достаточной скоростью. Библиотека Direct3D — впечатляющий набор программных модулей, оптимизированных с расчетом на максимальную производительность.
Быстродействие, тени, фреймы и координаты
ПРИМЕЧАНИЕ
Все трехмерные объекты, с которыми нам придется иметь дело, составлены из плоских многоугольников — чаще всего из треугольников. Эти многоугольники являются гранями объекта. А почему они должны быть непременно плоскими? Только для того, чтобы компьютеру было проще и быстрее рисовать их. Некоторые механизмы визуализации могут работать и с криволинейными гранями, однако используемая нами система не входит в их число. Мы научимся создавать объекты в главе 4.
Что ж, прекрасно — мы похвалили нашу библиотеку, но вы еще не уловили, к чему я клоню? Разумеется, для такого потрясающего быстродействия пришлось кое-чем пожертвовать. Например, на картинке нет ни теней, ни отражений. Возможно, вы даже не обратили на это внимания? Снова запустите программу-пример и понаблюдайте за вращением сфер. Взгляните, откуда падает свет — красная и синяя сфера проходят между белой сферой и направленным источником света, однако на белой сфере не появляются тени. Кроме того, вы не увидите на большой сфере отражений малых сфер.
Механизм визуализации Direct3D не осуществляет трассировки лучей, поэтому он не умеет генерировать теней и отражений. Как следствие, мы получаем значительное увеличение быстродействия. Если вы не заметили отсутствия теней до того момента, когда я вам об этом сказал, то согласитесь с тем, что впечатляющие трехмерные анимации могут обходиться и без теней с отражениями. Как мы узнаем позднее, на самом деле можно генерировать тени и даже имитировать отражения при помощи методики, которая называется хромовым покрытием (chrome wrap), рассмотренной в главе 8, — так что не огорчайтесь и продолжайте читать дальше.
Как видно из текста программы на стр. 22, для вращения большой белой сферы в нашем макете применен вызов функции SetRotation. При запуске приложения можно убедиться, что белая сфера вращается — затенение выполнено настолько качественно, что это даже не сразу видно. Тем не менее можно сразу же заметить, что две меньшие сферы вращаются вокруг большей. Но где же программный код, вызвавший их вращение? Секрет заключается в том, как они были присоединены к макету. Обратите внимание на то, что большая сфера присоединялась непосредственно к макету:
scene.AddChild(&shl) ;
тогда как меньшие сферы присоединялись к большой сфере:
scene.AddChild(&sh2) ;
scene.AddChild(&sh3) ;
Благодаря этому меньшие сферы начинают вращаться вместе с ней. Возможность иерархического закрепления объектов часто встречается в системах трехмерного синтеза изображений.
Для каждого объекта в создаваемом нами макете существует связанный с ним фрейм. Фрейм представляет собой описание математического преобразования (положения, размера или характера объекта), которое должно быть применено ко всем объектам и фреймам, присоединенным к данному фрейму, перед
26 Глава 1. Наше первое трехмерное приложение
их обсчетом. К фрейму могут присоединяться и другие фреймы-потомки; соответствующие им преобразования выполняются после родительских. В результате фреймы-потомки перемещаются вместе с родительским фреймом и также могут обладать самостоятельным движением по отношению к родителю. Чтобы лучше понять это, представьте, что вы расхаживаете по офису внутри большого здания. Ваш фрейм — это офис, в котором вы находитесь. Родительским фреймом офиса может быть целый этаж, а родительским фреймом этажа — все здание, в котором вы находитесь. Хотя вы замечаете только свое движение по офису, вы также перемещаетесь относительно этажа и всего здания. Эта тема будет подробно рассмотрена в главе 5, где мы займемся преобразованиями, и в главе 6, где показано, как происходит движение объектов.
При реализации библиотеки объектов 3dPlus я решил закрепить за каждым макетом отдельный фрейм. Каждая фигура и источник света также обладают собственным фреймом. Таким образом, вы можете взять произвольный набор объектов и присоединить их к общему фрейму или друг к Другу так, как сочтете нужным.
Для чего же все это нужно? Существует множество эффектов, которые чрезвычайно просто реализуются при помощи иерархии объектов/фреймов. Давайте рассмотрим космический тяжелый танк Mark VII, в котором, как известно, для наведения пушек используется допплеровский радар, работающий в Х-диапазоне. Если бы нам понадобилось смоделировать этот танк в нашем трехмерном приложении, мы бы создали геометрическое тело, изображающее радар, присоединили его к башне танка и заставили вращаться вокруг своей оси. Если описывать ситуацию в терминах фреймов, то фрейм радара становится потомком по отношению к фрейму башни. После этого можно сосредоточиться на перемещении тапка и забыть про радар — он всегда будет находиться в правильном положении и вращаться. На Рисунок 1-3 изображен танк Mark VII в действии.
Рисунок* 1-3. Космический тяжелый танк Mark VII с допплеровским радаром
'^fi^sfe 07
Быстродействие, тени, фреймы и координаты ^И •-
Последнее, о чем мне хотелось бы упомянуть в этой главе, — это система координат. Поскольку мы работаем с тремя измерениями, положение каждой точки представляется тремя координатами. У нас имеются три оси — х, у и z, организованные в так называемую левостороннюю систему координат. Давайте проведем небольшой эксперимент (если вы читаете эту книгу, лежа в кровати, то предупредите свою лучшую половину о том, что вы сейчас начнете делать странные жесты руками — иначе происходящее может быть воспринято как намек). Вытяните левую руку перед собой и выпрямите пальцы; ладонь обращена вправо, а большой палец находится сверху. Поднимите большой палец, затем подогните мизинец и безымянный палец к ладони и отведите средний палец вправо. Ваша рука должна выглядеть примерно так, как показано на Рисунок 1-4; большой палец изображает ось у, указательный — ось z, а средний — ось х.
Левая рука с левосторонней системой координат
В левосторонней системе координат ось у направлена вверх, ось х — вправо, а ось z — в глубь экрана (от пользователя). Разумеется, название обусловлено вовсе не тем, что вы можете превратить свою левую кисть в какую-то странную фигуру. Если взять винт с левой нарезкой и вращать его от оси х к оси у, он начнет двигаться по направлению оси z. Для того чтобы это правило работало в левосторонней системе координат, понадобится винт именно с левой нарезкой.
Многие трехмерные программы основаны на правосторонней системе координат, однако к нашему случаю это не относится, так что привыкайте смотреть на свою левую руку в тех случаях, когда вам нужно сообразить, какая ось куда направлена. Если вы всей душой ненавидите левостороннюю систему координат и страстно желаете перейти к правосторонней, это не так уж сложно. Стоит добавить простейшее преобразование к правосторонней системе координат, и она превратится в левостороннюю, используемую механизмом визуализации. Такие преобразования рассматриваются в главе 5.
Глава 1. Наше первое трехмерное приложение
Координаты точки в трехмерном пространстве могут передаваться в программные модули DirectX одним из двух способов. Иногда координаты передаются в виде тройки значений типа double, соответствующих координатам х, у и z, а иногда — в виде структуры D3DVECTOR, членами которой являются те же самые координаты. В любом случае координаты представляются значениями с плавающей точкой. Выбор масштаба оставляется исключительно на усмотрение пользователя, но я решил установить камеру и другие параметры сцены так, чтобы единичный куб, помещенный в точку 0,0,0 сцены, имел нормальные размеры. Позднее мы снова вернемся к координатам и всему, что с ними связано.
ПРИМЕЧАНИЕ
Библиотека 3dPlus содержит класс C3dVector, являющийся производным от класса D3DVECTOR. Всюду, где в качестве аргумента функции выступает тип D3DVECTOR, вместо него можно использовать объект C3dVector. Я создал класс C3dVector, поскольку класс C++ в программе приносит больше пользы, чем простая структура. Кроме того, вы можете обратить внимание на то, что функции Direct3D получают аргументы типа float, а не double. Я использовал значения типа double в своем коде, потому что они дают большую точность, легче преобразуются и используются во всех математических runtime-библиотеках С.
Вы еще не передумали?
Вероятно, вам уже надоело читать и размахивать руками. Вы бы предпочли запустить Visual C++, скопировать проект Basic с диска CD-ROM (или запустить программу Setup) и поэкспсриментировать с приложением, заставив его делать что-нибудь другое. Можно попробовать изменить цвета фигур, цвет-освещения, параметры вращения и даже добавить к макету несколько новых фигур — например, кубов, конусов или цилиндров. Класс C3dShape содержит функции для создания всех этих простейших геометрических тел. Но перед тем, как браться за дело, стоит поближе познакомиться с примерами и необходимой настройкой среды разработки.
Перед тем как компилировать программу-пример, необходимо правильно настроить среду разработки. Это делается следующим образом:
1. Запустите программу Setup для DirectX 2 SDK и установите средства разработки, входящие в DirectX 2 SDK. При этом на ваш жесткий диск будут перенесены включаемые файлы, библиотеки DirectX 2 SDK и т. д. Кроме того, будут установлены runtime-библиотеки DirectX 2 SDK, если это не было сделано ранее.
2. Запустите Visual C++ и выполните команду Tools ¦ Options; выберите вкладк\ Directories в окне диалога Options.
3. Добавьте путь к включаемым файлам DirectX 2 SDK в список Include Files, a путь к библиотекам DirectX 2 SDK — в список Library Files. Если вы забудете сделать это, то получите сообщения об ошибках на стадии компиляции или компоновки.
Вы еще не передумали?
ПРИМЕЧАНИЕ
В заголовочных файлах Direct3D содержатся ссылки на два файла— subwtype.h и d3dcom.h, которые не используются при построении Windows-приложений и соответственно не входят в SDK. К сожалению, при проверке взаимосвязей Visual C++ обнаруживает, что эти файлы могут понадобиться, и жалуется на их отсутствие. В качестве решения проблемы я создал два фиктивных файла: subwtype.h и d3dcom.h. Они находятся во включаемом каталоге библиотеки 3dPlus. В этих файлах нет ничего, кроме краткого комментария.
Во всех примерах используется библиотека 3dPlus, поэтому перед построением приложений-примеров вы должны скопировать на жесткий диск по меньшей мере ее включаемые файлы и библиотеки. Проще всего скопировать все дерево каталогов с примерами 3dPlus. В этом случае вы сможете перекомпилировать приложение перед тем, как запускать его — это позволит убедиться, что все необходимые файлы находятся на месте. Если же вы воспользуетесь программой Setup с прилагаемого диска, то вам даже не понадобится вручную копировать каталоги 3dPlus. Тем не менее вам все же придется включить каталог 3dPlus\lndude в список Include Files и каталог 3dPlus\Lib — в список Library Files. При использовании принятых по умолчанию параметров Setup списки со включаемыми и библиотечными файлами должны выглядеть следующим образом:
С \MSDEV\INCLUDE
С \MSDEV\MFC\INCLUDE
С \DXSDK\SDK\INC
С \3D\3DPLUS\IMCLUDE
С \MSDEV\LIB С \MSDEV\MFC\LIB С \DXSDK\SDK\LIB С \3D\3DPLUS\LIB
Разумеется, вы можете самостоятельно задать все пути, по которым компилятор будет искать файлы. Я оставил их в таком виде, чтобы свести хлопоты к минимуму. Дерево каталогов на вашем компьютере должно выглядеть следующим образом:
С:\ 3D
3dPlus Include Lib Source Basic Color ... (остальные примеры) Dxsdk (DirectX 2 SDK) sdk
inc
30 Глава 1. Наше первое трехмерное приложение
lib
Msdev (Visual C++)
После того как вы настроите параметры среды и все будет готово к построению проекта, не забудьте выполнить команду Build ¦ Update All Dependencies Visual C++ и убедиться в том, что компилятор находит все заголовочные файлы.
Что же мы узнали в этой главе?
Если вы ответили: «Почти ничего», значит, все мои усилия не произвели на вас особого впечатления. Я надеялся на что-нибудь вроде «Да, мое первое трехмерное приложение действительно оказалось простым» или «Всю жизнь мечтал посмотреть на окно с летающими шариками, и теперь моя мечта сбылась». Впрочем, каждому — свое. Конечно, вы уже готовы задать тысячу вопросов — чем мы будем заниматься теперь, как работают графические библиотеки, как нарисовать слона, как изобразить полет вокруг планеты, сопровождаемый величественной музыкой, как наложить фото президента Никсона на куб и чем вооружен космический танк Mark VII? Все это (и многое другое) будет рассказано в последующих главах. Вернее, почти все — у меня не нашлось портрета Никсона, а танки еще не завезли на склад.
В следующей главе мы приступим к построению приложения с более слож ной структурой, к которому мы будем добавлять новые возможности по мере знакомства с материалом книги. Кроме того, мы посмотрим, как устроен механизм визуализации, и начнем знакомиться с основами его работы.
Глава 2 Расставляем декорации
Структура приложения
Основные принципы архитектуры, выбираемые в начале проекта, нередко оказывают значительное влияние на его развитие. Неудачная структура может привести к тому, что ваш проект станет «обрастать бородавками» быстрее, чем вы будете их удалять. Например, при написании книги «Animation Techniques for Win32» я еще не обладал достаточным опытом программирования на C++ и работы с Microsoft Visual C++ и библиотеками MFC. В начале работы над примерами я совершил то, что сейчас считаю своей грубой ошибкой: воспользовался Visual C++ для построения однодокументного (SDI) приложения и решил, что мне удастся как-нибудь приспособить его для своих целей. Я сделал это лишь потому, что на тот момент приходилось выбирать между однодокументным и многодокументным (MDI) типами приложения, а MDI-приложение явно не подходило для воспроизведения игровой анимации. Сейчас я ясно понимаю, что мог бы существенно упростить все свои примеры, если бы отказался от принятой в Visual C++ метафоры «документ/вид» и воспользовался простым окном с меню.
На этот раз я решил, что структура моих приложений-примеров должна быть проще и ближе к той, которая может понадобиться при разработке компьютерной игры (это вовсе не означает, что она не годится для более серьезных целей — просто я отказался от использования метафоры «документ/вид» для упрощения программы).
В сущности, как мы вскоре убедимся, используемый нами оконный объект может выступать в роли главного окна приложения, как это было в примере
Структура приложения 'Чр!' 33