Текст
                    ИНТЕРАКТИВНАЯ
ТРЕХМЕРНАЯ МАШИННАЯ ГРАФИКА


INTERACTIVE 3D COMPUTER GRAPHICS L Ammeraal Hogeschool Utrecht The Netherlands John Wiley & Sons Chichester • New York • Brisbane • Toronto • Singapore
Серия МАШИННАЯ ГРАФИКА НА ЯЗЫКЕ СИ Л. Аммерал ИНТЕРАКТИВНАЯ ТРЕХМЕРНАЯ МАШ И Н Н АЯ ГРАФИ К А Перевод с английского языка В.А. Львова Москва "Сол Систем" 1992
ББК 32.97 А 62 УДК 681.3 Л. Аммерал А 62 Интерактивная трехмерная машинная графика. Пер. с англ. — М.: "Сол Систем", 1992. — 317 стр.: ил. ISBN 5-85316-003-6 (рус.) Описывается интерактивная программная система для работы с пространственными объектами: формирование, преобразование, полу- чение проекций с удалением невидимых линий, в том числе для криво- линейных поверхностей. Вывод изображений на экран монитора, матричный принтер и графопостроитель. Система работает на персо- нальном компьютере, реализована на языке Си Для широкого круга читателей, применяющих персональные компьютеры IBM PC или совместимые с ними для работы с графической информацией. 2404090000—007 И68(03)—92 (безобъявл.) Издание подготовлено при участии МП "БИНОМ" © 1986, John Wiley & Sons © 1992, перевод на русский язык, оформление Sol System Ltd. © 1992, переработка программ Sol System Ltd. Подписано в печать 11.08.92. Формат 84Х Ю8/32. Тираж 30 000 экз. Зак. 273. «Сол Систем». Москва, 103104, ул. Остужева, д. 12/2, кв. 6. Отпечатано с диапозитивов на Книжной фабрике № 1 Министерства печати и информации РФ. 144003, г. Электросталь Московской обл., ул. Тевосяна, 25. ISBN 5-85316-003-6 (рус.) ISBN 0-471-92014-2 (англ.)
ПРЕДИСЛОВИЕ Как и сами компьютеры, машинная графика и автоматизи- рованное проектирование могут рассматриваться с двух различ- ных позиций. Есть пользователи у которые интересуются лишь внешними аспектами графического программного обеспечения, и есть такие люди, которые больше хотят знать о его внутреннем содержании, поскольку они намерены сами писать подобное про- граммное обеспечение. Последнюю группу будем в общем назы- вать программистами, имея в виду, что к ним относятся и те люди, которые, имея богатый программистский опыт, обычно называют себя как-либо по другому. В отличие от предыдущих книг автора, опубликованных в издательстве John Wiley & Sons и ориентированных главным образом на программистов, эта книга написана для обеих групп читателей: главы 1 и 2 предназначены преимущественно для пользователей, а главы 3 и 4 — для про- граммистов. Если читатель собирается углубленно изучить алго- ритмы и исходные тексты программ, ему все же настоятельно рекомендуется ознакомиться также и с главами 1 и 2, поскольку необходимо знать для чего предназначены изучаемые програм- мы, прежде чем разбираться, как они работают. С другой сторо- ны, если читатель относится к категории пользователей и воз- можно совсем не интересуется программированием,.он сможет оценить тот факт, что в книгу включены полные исходные тек- сты всех программ, описываемых в книге. Очень часто обнару- живается, что пользователям очень нравятся те или иные про- граммы, за исключением одного или двух важных моментов, которые хотелось бы изменить. Если иметь дело с программным обеспечением, представленным в данной книге, то можно обой- тись без автора программы (хотя автор книги будет благодарен,
6 ПРЕДИСЛОВИЕ если ему сообщат о каких-либо возникших проблемах), или про- сто обратиться к любому специалисту по программированию на языке Си, который достаточно грамотен, чтобы разобраться в возникшей проблеме. Сложная программа во многом похожа на цепь, которая прочна так же, как ее слабейшее звено, и чем больше имеется средств для замены слабого звена, тем лучше. Последнее замечание не означает, что автор ожидает каких- ли бо неприятностей. Автор применял программу D3D, составля- ющую основное содержание данной книги, для работы с разнооб- разными трехмерными объектами и не встретился с серьезными затруднениями. Для ознакомления с возможностями программы D3D, рекомендуется просмотреть иллюстрации в конце каждой из первых трех глав. По сравнению с другими книгами общего характера по ма- шинной графике, здесь учитываются специфические особенно- сти применяемых технических средств и базового программного обеспечения. Для работы необходимо иметь персональный ком- пьютер фирмы IBM (XT или AT), или систему IBM PS/2, или другие совместимые с ними компьютеры. Очевидно, что нужно иметь в наличии графический адаптер (типов CGA, EGA, VGA или HGA). Что касается программного обеспечения, то все про- граммы написаны на языке Си. Из возможных компиляторов этого языка в книге применяется Турбо-Си фирмы Borland, который очень выгоден из-за его дешевизны. Если читатель является простым пользователем системы автоматизированного проектирования и не знаком с компиляторами и другими подоб- ными делами, то он может просто заказать соответствующую ди- скету с программами. На дискете имеются исходные тексты про- грамм (которые включены для экономии времени на набивку этих программ), а также выполняемые модули программ (файлы типа ххх.ЕХЕ), так что пользователь сразу же может запускать эти программы на выполнение. При этом не потребуется даже беспокоиться о "процедуре установки" — программа сама опре- делит, какой графический адаптер имеется в системе. В учебной аудитории графический результат работы про- граммы на персональном компьютере можно показать непосред- ственно сразу всем студентам (например, путем проецирования через кодоскоп) и программа D3D может оказаться полезной при изучении прикладной математики и ряда других предметов.
ПРЕДИСЛОВИЕ 7 Автор подчеркивает, что при обсуждении и решении таких математических проблем, как поворот объектов в трехмерном пространстве, формирование пространственных кривых, отобра- жение сплошных непрозрачных тел, выбор способов аппрокси- мации сферы, описание поверхностей с помощью В-сплайнов и удаление невидимых линий, многое зависит от конкретных тех- нических средств. Поэтому персональный компьютер можно считать просто примером инструмента, который практически реализует математические концепции. Язык программирования Си хорошо известен своей мобиль- ностью, а компилятор Турбо-Си поддерживает как классический стиль Кернигана и Риччи, так и современный стандарт ANSI. Основное различие между этими двумя стилями подробно описывается в параграфе 3.1. Данная книга очень тесно связана с двумя предыдущими книгами автора "Принципы программирования в машинной графике" (ППМП и "Машинная графика для персонального компьютера" ШГПЮ. Перспективное представление трехмер- ных объектов и удаление невидимых линий подробно разбира- лись в ППМГ, а графические функции нижнего уровня были предметом обсуждения в МГПК. Здесь эти важные темы затра- гиваются лишь в качестве первичного средства, используемого в программе D3D. Проблема удаления невидимых линий здесь представлена лишь в общих чертах, но реализована более эф- фективным образом. Аналогично, графические функции нижне- го уровня, исходные тексты которых приведены в параграфе 4.3, описаны очень подробно в книге МГПК, Однако в данной книге уделено некоторое внимание новым графическим средствам язы- ка Турбо Си, доступным только в версиях, начиная с 1.5. Здесь описывается альтернативный графический пакет нижнего уров- ня, нашедший практическое применение (параграф 4.3). Кроме того, теперь возможно получить вывод графической информа- ции на плоттер (графопостроитель), как будет описано в пара- графе 4.5. Многие иллюстрации в этой книге получены именно таким образом с помощью плоттера типа 7475А фирмы Hewlett- Packard, установленного в Высшей школе Утрехта, в отделе Ин- женерной механики, Хильверсум. Л. Аммерал
Чзсть I ИНФОРМАЦИЯ ДЛЯ ПОЛЬЗОВАТЕЛЯ Глава 1 ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ 1.1. КООРДИНАТЫ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ В этой книге описывается программа D3D для проектирова- ния объектов в трехмерном пространстве. Исходный текст этой программы на Турбо-Си, приведен в конце книги. Совершенно нет необходимости в тяжелой работе по ручному вводу этой про- граммы через клавиатуру компьютера, поскольку копию как исходного текста, так и выполняемого модуля можно получить на гибком диске у издателя книги. Благодаря наличию на диске- те выполняемого модуля эту программу можно использовать со- вершенно не зная программирования на языке Си и не применяя компилятора с этого языка. Если необходимо подробно ознако- миться с работой программы, приведенной в данной книге, то в главах 3 и 4 можно найти много полезного материала, который предполагает знакомство как с программированием, так и с при- меняемыми математическими средствами. Для чтения глав 1 и 2 не требуется специальных знаний по проблемам программирова- ния, поскольку эти главы написаны специально с ориентацией на пользователя программы D3D. Но все-таки нужно иметь представление об элементарных математических операциях, как это будет видно из остальной части текста этого раздела. В науке и технике принято использовать трехмерную систему координат с осями х, у и z, как показано на рис. 1.1. Такая систе- ма называется ортогональной (декартовой) системой координат:
/. /. КООРДИНАТЫ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ 9 Рис. 1.1. Ортогональные координаты любая пара этих трех осей взаимно перпендикулярна. Все три координатные оси проходят через одну общую точку начала ко- ординат О и являются бесконечно длинными. На рис. 1.1 пока- заны только малые части этих трех полуосей, в дальнейшем их будем называть положительными осями. Координаты представ- ляются в виде вещественных чисел. Точка Р имеет ортогональ- ные координаты jcp, ypy zp, смысл которых заключается в том, что, отправляясь из точки О, можно достичь точки Р двигаясь сначала в положительном направлении оси х на расстояние дср, затем на расстояние ;ур в направлении положительной оси у, и, наконец, на расстояние zp в направлении положительной оси z. Координатная системе, изображенная на рис. 1.1, является правой. Это означает следующее. Предположим, что положи- тельная ось х поворачивается на угол 90° вокруг оси z таким об- разом, что после этого поворота ось х совпадает с осью у; это вра- щение можно сравнить с вращением винта с правой (то есть нор- мальной) резьбой. При таком повороте винт будет несколько пе- ремещаться в направлении оси z. Существует масса различных способов размещения правой трехмерной системы координат в пространстве. Как показано на рис. 1.1, можно выбрать такое ее положение, что положительная ось z направлена вверх, это предопределяет расположение оси х и оси у в горизонтальной плоскости, так называемой плоскости ху. Кроме прямоугольных координат для наших задач иногда мо- гут оказаться полезными сферические координаты. Здесь также
10 Глава 1. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ Рис 1.2. Сферические координаты для определения положения точки Р используются три вещест- венных числа. Вместо обозначений jcp, ур, zp (или просто х> у, z) для обозначений сферических координат применяются буквы греческого алфавитар, 0, <р (ро, тета, фи). Как видно из рис. 1.2, значение р определяет расстояние между точками Р и О или, другими словами, это значение является радиусом сферы с цент- ром в точке О, проходящей через точку Р. Символами в и <р обозначаются углы, которые также показа- ны на рис. 1.2. Угол в измеряется в плоскости ху, используя поло- жение точки Р', являющейся проекцией точки Р на эту плоско- сть; точка Р' определяется путем опускания перпендикуляра из точки Р на плоскость ху. Значение в равно углу, на который по- требовалось бы повернуть положительную ось х (вокруг оси z) в положительном направлении до тех пор, пока она не будет про- ходить через точку Р' (говорят, что в правой системе координат вращение вокруг оси z будет в положительном направлении, ес- ли оно соответствует повороту винта, перемещающегося в нап- равлении положительной оси z). Например, угол в лежит между 0° и 90°, если точка Р' лежит в первом квадранте, то есть в облас- ти между положительной осью х и положительной осью у. Уголр — это угол, измеренный в вертикальной плоскости между осью z и прямой линией ОР. Значение угла <р может быть в пределах от 0° до 180°. Если у читателя есть некоторое пред- ставление о тригонометрических функциях синуса и косинуса, то он может установить следующие соотношения между
1.2. ОБЪЕКТ, ТОЧКА НАБЛЮДЕНИЯ И ПЕРСПЕКТИВНОЕ сферическими координатами/?, 6><р с одной стороны, и прямо- угольными координатами х, у, z с другой: х=р sin <p cos в у=р sin<p sin0 z=p cos<р 1.2. ОБЪЕКТ, ТОЧКА НАБЛЮДЕНИЯ И ПЕРСПЕКТИВНОЕ ИЗОБРАЖЕНИЕ При использовании программы D3D мы будем иметь дело с точками, отрезками прямых линий и, что наиболее интересно, с конечными сплошными телами, которые ограничены плоскими гранями. Кривые поверхности могут быть аппроксимированы набором плоских граней, что аналогично аппроксимации кривых последовательностью отрезков прямых линий. Такие ограничи- вающие грани могут быть образованы любым полигоном, воз- можно с отверстиями в них. Поскольку желательно с помощью компьютера получать перспективные изображения, то очевидно, что мы должны тем или иным способом определять позиции вер- шин этих полигонов. Для этой цели будем использовать прямо- угольные координаты в правой системе координат, которая была упомянута в параграфе 1.1. Нам не нужно будет беспокоиться о том, будет ли желаемое перспективное изображение объекта со- ответствовать экрану нашего компьютера. Поскольку все преоб- разования масштабирования и позиционирования выполняются автоматически, можно использовать любые единицы длины — дюймы, миллиметры или метры. Но при всех обстоятельствах следует соблюдать логичность в выборе одинаковых единиц из- мерения по всем направлениям. Это же относится и к способу за- дания позиции глаза, называемой точкой наблюдения. Для обозначения этой точки будем применять букву Е (от слова Eye — "глаз"). Точка Е имеет очень важное значение в ее соотноше- нии с. центральной точкой объекта О, которая располагается более или менее близко от центра объекта. Тогда прямая линия ЕО будет называться линией наблюдения, а направление от Е к О — направлением наблюдения. Как видно на рис. 1.3, для на- блюдения доступно все, что лежит в пределах определенного ко- нуса, ось которого совпадает с линией наблюдения ЕО. Для задания точки наблюдения Е относительно объекта вооб- разим новую систему координат с началом в центральной точке
12 Глава 1. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ Рис. 1.3. Конус и линия наблюдения • объекта О, каждая ось которой параллельна соответствующим исходным осям. Затем нам нужно указать сферические коорди- наты ру ви<р точки наблюдения Е относительно этой новой систе- мы координат. Тогда буквой р будет обозначена длина отрезка прямой линии ЕО, как это видно из рис. 1.3; другими словами это будет расстояние наблюдения. На рис. 1.3 также показана поверхность проецирования, которая представляется в виде плоскости, перпендикулярной линии наблюдения. Все видимые точки объекта посылают лучи света в глаз Е. Пересечение этих лучей с плоскостью проекции образуют перспективное изобра- жение, которое в данном случае нас в основном и интересует. Такой способ проецирования объекта на плоскость называется центральным проецированием, поскольку все проецирующие лучи проходят через точку наблюдения Е — центр проекции. Очевидно, что расстояние между плоскостью проекции и точ- кой наблюдения Е определяет размер изображения. В программе D3D это расстояние будет выбираться автоматически таким об- разом, чтобы изображение всего объекта хорошо совпадало с раз- мером нашего экрана, так что пользователю не нужно будет бес- покоиться о расположении плоскости проекции. Угол а между осью конуса и его образующей должен быть до- статочно малым, чтобы перспективное изображение выглядело приемлемом образом для большинства пользователей. Это до- стигается выбором расстояния наблюдения р значительно боль- ше, чем размер объекта. Например, если хотим получить изобра-
1.2. ОБЪЕКТ, ТОЧКА НАБЛЮДЕНИЯ 13 жение куба с единичной длиной стороны, тогда можно рекомен- довать значениер > 5. Для некоторых очень больших значений, скажем р = 100 000, изображение будет примерно таким же, как и при значительно меньших значениях р. Тогда почему бы всегда не выбирать такие большие значения расстояния наблюдения? Ответ заключается в том, что при очень большом расстоянии/? фактически получится не перспективное изображение, а парал- лельное — все линии, параллельные на объекте, будут также параллельными и на изображении. Для понимания этого явле- ния представим, что в последнем случае угол а на рис. 1.3 будет таким малым, что все лучи света, выходящие из различных точек объекта и проходящие через точку наблюдения Е будут практически параллельными. Таким образом при очень больших значениях расстоянияр будетполучена приблизительно парал- лельная проекция, которая очень часто применяется на практи- ке, поскольку ее проще использовать, чем реальную перспектив- ную проекцию. На рис. 1.4. показаны три представления куба с единичной длиной стороны, которые получаются при различных значениях расстояния р. Многие предпочитают изображение на рис. 1.4(a) при/? = 5. На этом рисунке видно, что ребра изображаются в виде более ко- ротких отрезков, если они расположены дальше, что может быть полезным при интерпретации сложного изображения (особенно, если объект представляется в виде проволочной модели). Нет ни- чего существенно неверного в изображении рис. 1.4(6), где при- нято значение/? = 100 000, но это изображение может показаться слишком упрощенным, поскольку в нем никак не проявляется эффект перспективы. Параллельные ребра в кубе имеют отобра- жение в виде параллельных линий на рис. 1.4(6) и отрезки пря- мых линий не сокращаются, если они более удалены (фактиче- ски на изображении имеются сокращенные отрезки, но это прои- зошло из-за их положения в пространстве, а не из-за удаленнос- ти от точки наблюдения) ..Строго говоря, здесь все-таки остается некоторый эффект перспективы, поскольку расстояние/?, хотя и очень большое, но не бесконечно велико и теоретически есть не- большая разница в длине отрезков для параллельных ребер куба. Однако эти различия слишком малы, чтобы их заметить, поэто- му их можно проигнорировать и говорить, что проекции любых двух параллельных ребер куба параллельны и имеют одну и ту
14 Глава 1. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ (в) Рис. 1,4. (a)p-J. (6)р-10000, (в) р-2
7.2. ОБЪЕКТ, ТОЧКА НАБЛЮДЕНИЯ 15 же длину. В этом отношении можно рассматривать параллель- ную проекцию как специальный случай перспективной проек- ции, а не как какую-то фундаментально отличную форму. Так что наша программа D3D может быть использована для получе- ния изображений не только перспективных проекций, но также и для параллельных проекций, что и демонстрируется на рис. 1.4(6). Куб на рис. 1.4(b) прир = 2 выглядит совершенно неесте- ственно. Здесь мы видим преувеличенный эффект перспективы, поскольку значение р выбрано слишком малым. Поэтому кажет- ся совершенно необходимым предупредить о нежелательности задания слишком малых расстояний р. Что касается углов в и<р, то для них могут быть заданы любые значения. Если поддержи- вать значения р и <р постоянными и изменять значение в от 0° до 360°, то положение точки глаза перемещается вокруг объекта в горизонтальной плоскости на полный оборот. Если выбрать^? = 0, то точка наблюдения лежит непосредственно сверху над объек- том, а при (р = 90° линия наблюдения направлена горизонтально. В большинстве практических случаев используется некоторое значение угла <р между 45° и 90°, что соответствует расположе- нию глаза несколько выше объекта. Это и сделано для формиро- вания рис. 1.4, где использованы одинаковые значения углов в - 20е и <р а 70° для всех трех представлений куба. Если в распоряжении читателя имеется программная диске- та, прилагаемая к данной книге (поставляется издателем отдель- но), то можно немедленно использовать файл EXAMPLE1.DAT для проведения экспериментов с различными позициями точки наблюдения. Программа запускается вводом команды D3D или, что совершенно эквивалентно, d3d В остальной части этой книги для обозначения команд будут использоваться прописные буквы. При таком способе команды будут четко отличаться от нормального текста, обычно состоя- щего из строчных букв. Однако при желании любая команда мо- жет быть введена строчными буквами. После запуска программы D3D на экране появляется некото- рая общая информация. Прочитав ее, можно просто нажать кла-
16 Глава L ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ вишу Enter ("Ввод"). Затем на экране появляется изображение, показанное на рис. 1.5. Как видим, экран разделен на две части: в одной (левой) будет появляться интересующее нас изображе- ние трехмерного объекта, а в другой — меню команд, сообщения и данные, вводимые с клавиатуры. Будем называть эту область областью сообщений. Для быстрого получения результата можно ввести команду: R которая является сокращенным обозначением команды Read ("Чтение"). Она вызовет вывод сообщения: Входной файл: запрашивающего ввод имени файла, содержащего вводимые данные. Если теперь ввести имя EXAMPLE1.DAT (ввод заканчивается нажатием клавиши Enter — "Ввод"), то на экране получим изображение, показанное на рис. 1.6(a). Можно высказать удивление, что не все линии имеют одина- ковую толщину. Действительно, линии объекта, близкие к точке наблюдения отображаются более толстыми линиями, чем более удаленные. Это помогает лучше интерпретировать изображение 2 X • У Программа D30 Доступные команды '. X/Y/Z Управл.курсором ? Значения координат М-режим (А-оси и т.д.) FKfloMOUb)! Q-выход S-uiar 1 С-очиститъ 1-ввод 1 D-удалить F-грани I L-список R-чтение I W-эапись Е-весь экр1 Н-невидимы V-т.зрения! Т-преовр. Вводите команду! Рис. J.5. Начальное изображение на экране
1.2. ОБЪЕКТ, ТОЧКА НАБЛЮДЕНИЯ 17 проволочной модели, нежели при одинаковой толщине изобра- жения всех линий. Вполне вероятно, что иногда эффект может оказаться странным, но при желании всегда можно получить изображение проволочной модели с одинаковой толщиной линий в окончательном результате, как это увидим в параграфе 1.5. Получив рис. 1.6(a), можно сформировать и другие изобра- жения объекта путем изменения положения точки наблюдения. Позиция этой точки задается в сферических координатах, пока- занных на рис. 1.2. В начале работы программы точка наблюде- ния по умолчанию имеет сферические координаты р = 1 000, 0 = 20°, <р = 75°. Точку наблюдения можно поместить в любую точку пространства (при условии, что она расположена вне объ- екта) и таким образом получить любой вид изображения. Изме- нение положения точки наблюдения производится по команде V Изображение на экране становится подобным рис. 1.2, чтобы напомнить смысл сферических координатр, 0, <р и показать по- ложение точки наблюдения. Одновременно текущие значения координат отображаются в числовой форме. Для каждой из них ожидается либо ввод нового значения, либо нажатие на клавишу "Ввод", что предполагает оставление текущего значения без из- менения. Заметим, что значения углов 0 и <р должны быть выра- жены в градусах. Как только значения параметровр, 0, <р опреде- лены тем или иным образом, на экране появится перспективное изображение, соответствующее заданной точке наблюдения. Очень полезно поиграть некоторое время с этой программой и сформировать несколько видов изображений объекта данного примера. Можно видеть, если еще необходимо, что эффект параллельного проецирования получается просто путем задания достаточно большого значения/?, например, 100 000. Кроме того очень легко получить виды объекта спереди, сбоку и сверху, которые обычно используются в инженерном черчении, они показаны на рис. 1.6 (б), (в) и (г). Все, что для этого нужно сде- лать, это опять задать очень большое значение для р и выбрать значения 0 и <р следующим образом: Вид спереди (вид с положительной оси х): 0 = 0°, <р = 90° Вид сбоку (вид с положительной оси у): 0 = 90°, <р = 90° Вид сверху (вид с положительной оси z): 0 = 0°, <р = 0°
18 Глава I. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ Программа DX Доступные команды : X/Y/Z ^равл.курсором ? Значения координат М-режим (А-оси и т.д.) F1 (Помои*) 1 Q-выход S-uiar I С-очистить I-ввод I О-удалить F-грани I L-список R-чтение I W-запись Е-весь экр I Н-невидимы V-т.зрения! Т-преоВр. Вводите команду: (а) Программа ОХ Доступные команды : X/Y/Z Управл.курсором ? Значения координат М-режим (А-оси и т.д.) FKIbMOua») I Q-выход S-uiar I С-очистить I-ввод I D-удалить F-грани I L-список R-чтение I W-эапись Е-весь экрI Н-навидимы V-т.зрения I T-npeoBp. Вводите команду! (б) Рис. 1.6. (а) Перспективное изображение, (б) — вид спереди; (в) — вид сбоку; (г) — вид сверху
1.2. ОБЪЕКТ, ТОЧКА НАБЛЮДЕНИЯ И ПЕРСПЕКТИВНОЕ 19 / Программ D30 1 Доступные команды ! X/Y/Z Управл.курсором *? Значения координат М-режим (А-оси и т.д.) FKIbMOUt»)! Q-еыход S-шаг 1 С-очистить 1-ввод 1 D-удалить F-грвии I L-список R-чтенив 1 W-эались Е-ввсь экр| Н-нееиоимы V-т.зрения! Т-првобр. Вводите команду! _ (в) *- Программа ГЙО Доступные команды ! X/Y/Z Управл.курсором ? Значения координат М-ре«им (А-оси и т.д.) F1(Помои*)1 Q-выход S-шаг 1 С-очистить 1-ввод 1 D-удалитъ i F-грани 1 L-список R-чтгнив 1 W-эались Е-весь экр1 Н-ыевиоимы V-т.зрения! Т-яреобр. Вводите команду! _ , (Г) Рис. 1.6 (продолжение). (в) — вид сбоку; (г) — вид сверху
20 Глава 1. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ Эти виды применяются в инженерном черчении не только потому, что их достаточно просто вычертить вручную%но и пото- му, что все отрезки прямых линий, перпендикулярные направ- лению проецирования, имеют натуральную величину в изобра- жении (не подвергаются уменьшению). В этом разделе было показано, что нашу программу можно "запустить", просто напечатав ее имя D3D. Конечно, нужно знать и как "выйти" из этой программы. Во многих аналогичных программах для этой цели применяется команда Quit ("Выход") с сокращением до одной буквы Q 1.3. РАБОТА С ТОЧКАМИ И УПРАВЛЕНИЕ КУРСОРОМ Рассмотрим теперь возможность проектирования и вычерчи- вания новых объектов вместо считывания из файла существую- щих объектов, как это делалось в предыдущем параграфе. Как и ранее, изображения трехмерных объектов будут формироваться из отрезков прямых линий. Их можно вычертить только в том случае, если известны координаты обоих концов отрезка, поэто- му сначала рассмотрим только точки. При определении точки будем говорить, что точку вводим в трехмерном пространстве, для чего можно воспользоваться командой /. Фактически в обла- сти сообщений на экране эта команда обозначена именем Insert ("Ввод"), но сразу же после ввода первой буквы I этого слова, появится запрос на ввод четырех чисел п х у' z Например, фактически может быть введен ряд чисел: 5 1.49 0.8 3 После ввода числа 3 в этой строке компьютер должен узнать, что Это последняя цифра в строке, для этого необходимо нажать на клавишу Enter ("Ввод"). Далее в подобных ситуациях больше не будем явно указывать на необходимость такого нажатия. Полез- но запомнить: если приходится довольно долго ожидать реакции программы и кажется, что ничего не происходит, всегда следует
1.3. РАБОТА С ТОЧКАМИ И УПРАВЛЕНИЕ КУРСОРОМ 21 попытаться нажать на клавишу Enter ("Ввод"). (Если такое нажатие окажется излишним, то может появиться сообщение "Неверная команда" и это сообщение можно проигнорировать). Только что введенные числа jc, у, z являются декартовыми коор- динатами новой точки. Число п является просто положительным целым числом, по которому впоследствии можно будет вызвать эту точку. Кроме всего прочего, для идентификации точки гораз- до проще использовать целое число (как 5 в данном примере), чем три вещественные координаты дс, у, z (в данном случае 1.49, 0.8, 3). Затем введенная новая точка (точка 5) отображается на экране, как показано на рис. 1.7. Кроме самой точки на рисунке виден перпендикуляр, опущенный из точки на плоскость ху в точку Ос, >', 0), чтобы получить более четкое представление о положении точки (дс, у, z) в трехмерном пространстве. Одновре- менно на экране будет также высвечен номер точки (5). Во время процесса конструирования нового объекта жела- тельно отображать и координатные оси, и номера точек, которые исчезают при формировании окончательного результата. Оси и номера всегда можно показать или удалить с помощью команды М что будет описано в конце параграфа 1.4. ; X : с У' Программа 030 Уступные команОы '. X/Y/Z Управл.курсором ? Значения коорОичат М-режим (А-оси и т.а.) F1(Помощь)1 Q-выхсэ S-шаг I С-очистить 1-ввоэ 1 Обвалить Р-граяи I L-список R-чтвиив I W-эапись Е-весь экр1 Н-нввивимы V-т.зрения! Т-првобр. ВвоЗите команву: _ Рис. 1.7. Точка, введенная по команде I
22 Глава 1. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ Поскольку обычно вводится большое количество точек, нет необходимости давать команду / для каждой из них. Программа D3D помнит, что установлен режим ввода точек до тех пор, пока не будет задана какая-либо другая команда. Если, после ввода нескольких точек, была использована другая команда, то возоб- новить ввод точек можно будет только после повторного ввода команды /; если об этом будет забыто, то ввод первой же цифры вызовет появление на экране сообщения: Нельзя начинать цифрой Это кажется неудобным, но так можно наилучшим образом избе- жать сложностей, связанных с командой F, как увидим в пара- графе 1.4. Точки могут быть удалены по команде Delete ("Удалить"), обозначаемой сокращенно D После ввода этой команды удаляются все точки, номера кото- рых лежат между двумя заданными границами. Для этого необ- ходимо ввести значения для нижней и верхней границ, тогда все точки с номерами не меньше нижней и не более верхней границы будут удалены. Если нужно удалить только одну точку, то в от- вет на запрос Нижняя граница нужно ввести номер точки, а затем просто нажать на клавишу Enter — "Ввод", когда на экране появится строка сообщения Верхняя граница Если заданный диапазон включает по крайней мере одну точку, то на экране появится новое изображение, из которого такие точ- ки будут исключены. Было бы очень неприятно, если сформированный объект ока- жется полностью уничтоженным в результате ошибочного нажа- тия на клавишу Z>, после чего неизвестно, что делать дальше. Поэтому команда D реализована безопасным образом — в ка- честве предлагаемой нижней границы показывается число, кото- рое на единицу больше самого большого номера точки, заданной до данного момента, так что при двойном нажатии на клавишу Enter после ввода команды D будет удалено пустое множество
1.3. РАБОТА С ТОЧКАМИ И УПРАВЛЕНИЕ КУРСОРОМ 23 точек (то есть не будет удалено ни одной точки). В сложных си- туациях, когда не все номера точек отображаются на чертеже в читаемой форме, чтобы узнать наибольший существующий но- мер точки, можно воспользоваться командой D несколько более хитроумным способом — после ввода команды D читаем на экра- не предполагаемую нижнюю границу и уменьшаем ее на едини- цу, затем дважды нажимаем на клавишу "Ввод". Если в списке очень много точек, а требуется узнать коорди- наты только одной из них, то можно ввести вопросительный знак ? что вызовет появление сообщения Номер точки: После ввода номера интересующей точки значения ее координат отображаются в области сообщений. Есть и другой способ ввода точек, который в большинстве слу- чаев более привлекателен, чем явный ввод значений координат. Можно использовать курсор, изображаемый на экране в виде небольшого квадрата. В двухмерной графике для перемещения курсора в четырех направлениях обычно используются четыре клавиши с соответствующими стрелками (влево, вправо, вверх, вниз). В трехмерной графике необходимо перемещать курсор в шести направлениях вместо четырех, поскольку имеются три оси и курсора должен двигаться по каждой из осей как в положитель- ном, так и в отрицательном направлениях. В программе D3D эта проблема решена путем использования клавиш X, У, Z в комби- нации с клавишами + и -. На большинстве клавиатур имеется по две клавиши плюса и минуса. Лучше использовать те из них, ко- торые расположены ближе к клавишам со стрелками, поскольку для них не требуется нажимать клавишу регистра Shift (что по- требовалось бы при использовании знака плюс, расположенного на той же клавише, что и знак равенства). Как только будет на- жата одна из клавиш X, У, Z, курсор появится на экране в точке начала системы координат. Перемещение курсора вдоль соответ- ствующей оси управляется клавишами + и -. Для изменения оси перемещения нужно снова ввести имя требуемой оси. По умолчанию шаг перемещения курсора выбран равным 0.2, но при желании его можно изменить с помощью команды S
24 Глава 7. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ ("Шаг"). Например, предположим, что нужно определить точку (0.6, 0.4, -0.2). Это можно сделать путем последовательного на- жатия клавиш: X+++Y++Z-I Следующая таблица показывает позиции курсора по шагам. Нажатая Позиция курсора виша X + + + Y + + Z - I X 0.0 0.2 0.4 0.6 0.6 0.6 0.6 0.6 0.6 0.6 У 0.0 0.0 0.0 0.0 0.0 0.2 0.4 0.4 0.4 0.4 z 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -0.2 -0.2 При нажатии клавиши / текущая позиция используется для ввода новой точки в этой позиции. Новой точке автоматически присваивается номер и он тут же отображается на экране. Для этого программа просто использует наименьшее положительное целое число, которое еще не применялось. Заметим, что позиция курсора после нажатия на клавишу / не изменяется, остается тем же самым и текущее направление (Z). Поэтому, если позиция следующей точки будет, например, (0.6,0.4, -0.4), то потребует- ся только однажды нажать на клавишу минус. Описанные действия в примере выглядят гораздо сложнее, чем они есть на самом деле. Во-первых, текущая позиция курсо- ра всегда отображается в числовой форме в области сообщений на экране, поэтому вместо скрупулезного счета количества шагов курсора можно просто читать его координаты и изменять их по желанию. Во-вторых, что более важно, практически никог- да не начинают с задания координат для определения точки в пространстве, есть другой способ работы. Обычно желательно разместить некоторые точки (и другие геометрические объекты) где-то в трехмерном пространстве так, чтобы их расположение было приятно для глаза, и координаты этих точек используются просто как средство описания позиций этих точек словесно или аналитически.
1.4. ОТРЕЗКИ ПРЯМЫХ ЛИНИЙ, ГРАНИ И ОБЪЕКТЫ 25 В некоторых случаях может оказаться полезным совместить курсор с точкой, которая была введена ранее. Самый быстрый способ — это поместить курсор так, чтобы он оказался ближе к этой точке, чем ко всем другим точкам, и нажать клавишу J Это вызовет "перескок" (Jump) к ближайшей ранее опреде- ленной точке, что исключает необходимость очень тщательной коррекции позиции курсора для совмещения с нужной точкой. Читатель мог заметить, что существуют две команды I: одна на уровне главного меню, отображаемого на экране, а другая — в режиме управления курсором (то есть при условии отображения курсора на экране). Это же применимо и к команде D для удаления точки. Но в режиме управления курсором команда D может быть использована только немедленно после команды /, которая гарантирует, что курсор находится точно в позиции точ- ки, которую нужно удалить. Кроме того, в данном случае за один раз можно удалить только одну точку. Кстати, если нужно удалить все введенные точки, то не обя- зательно удалять их последовательно одну за другой, а можно очистить весь экран одной командой Clear ("Очистить"): С Эта команда очень полезна, например, при проведении экс- перимента по применению программы D3D, когда требуется уда- лить все введенные точки, чтобы начать снова с чистым экраном для более серьезной работы. Эта команда необходима также в случае использования команды R для чтения файла, когда после анализа полученного изображения появилось желание сформи- ровать что-то свое, что может дать большее удовлетворение. 1.4. ОТРЕЗКИ ПРЯМЫХ ЛИНИЙ, ГРАНИ И ОБЪЕКТЫ Трехмерные объекты могут быть отображены в двух видах: в виде проволочной модели или в виде непрозрачного сплошного тела. Как видно из рис. 1.8, это означает, что для куба, взятого в качестве такого объекта, наблюдаемого из некоторой случайной точки, мы можем видеть либо все шесть, либо только три ограни-
26 Глава L ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ Рис. 1.8. Проволочная модель и сплошной объект чивающие грани. В терминах ребер, мы видим либо все двенад- цать, либо только девять из них. В дальнейшем будем применять оба типа изображения и, хотя начинаем с отображения прово- лочных моделей, будем формировать объекты таким образом, что наша программа будет способна удалять невидимые отрезки прямых линий, когда это потребуется. Предположим, что были определены все восемь вершин куба путем ввода их координат одним из способов, описанных в пре- дыдущем параграфе, то есть либо путем явного ввода номеров то- чек и значений их координат, либо с помощью управления кур- сором. В первом случае потребовалось бы ввести следующие восемь строчек текста после команды /: 1111 20 11 300 1 4 10 1 5 110 60 10 70 00 8 100 но практически эту работу более удобно выполнять с помощью управления курсором. В любом случае точки будут видны на эк- ране так, как изображено на рис. 1.9. (Конечно, фактическое изображение на экране также зависит от позиции точки наблю- дения. Для рис. 1.9 использовались принимаемые по умолчанию значения/? = 1000, в = 20°, <р = 75°).
1.4. ОТРЕЗКИ ПРЯМЫХ ЛИНИЙ, ГРАНИ И ОБЪЕКТЫ 27 Отрезки прямых линий и грани определяются вводом коман- ды Faces {"Грани'"): F после которой появляется сообщение Строка чисел с точкой: Если нас интересует только проволочная модель, то можно определить только две горизонтальные грани (четырьмя верши- нами каждая) и затем четыре вертикальные ребра (их двумя ко- нечными точками) следующим образом: 12 3 4. 5 6 7 8. 1 5. 2 6. 3 7. 4 8. Этот сравнительно простой набор входных данных вполне достаточен для вычерчивания проволочной модели куба. Однако потребуется несколько иной подход, если входные данные долж- ны быть записаны в таком виде, который необходим программе для вычерчивания куба в виде сплошного непрозрачного тела. Программа D30 Доступные команды ! X/Y/Z Управл.курсором 2 ? Значения координат I М-режим (ft-оси и т.а.) F1(Помои*)I Q-ewxod S-шаг I С-очиститъ 1-ввод I D-удалить I F-грани I L-список R-чтение I W-эапись E-BQCb экр| Н-нввидимы V—г.зрения! Т-преобр. Вводите команду: . Рис. 1.9. Вершины куба
28 Глава 1. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ Потребуется определить все шесть граней куба. Более того, все четыре вершины для каждой грани должны быть перечислены в порядке обхода в направлении против часовой стрелки при на- блюдении извне куба. Следовательно, вместо ввода предыдущих шести строк входных данных, предпочтительнее ввести строки: 12 3 4. 8 7 6 5. 8 5 1 4. 5 6 2 1. 6 7 3 2. 7 8 4 3. Чтобы понять запись второй строки из этих шести строк, сле- дует обратить внимание на следующее обстоятельство. Если рас- сматривать нижнюю грань не извне (то есть при расположении наблюдателя ниже куба), как требуется, а с более удобной пози- ции, расположенной несколько выше куба, чтобы эта грань была видна, как изображено на рис. 1.8 и 1.9, тогда указанная после- довательность точек 8 7 6 5 соответствует ориентации обхода по часовой стрелке. Можно сказать, что необходимые операции выполнены неправильно дважды. Во-первых, нижняя грань рассматривалась как бы через прозрачное тело куба, вместо того, чтобы глядеть на нее извне, и, во-вторых, вершины обходились по часовой стрелке, вместо обхода против часовой стрелки. Здесь исходили из того факта, что эти две "ошибки" компенсировали одна другую. Таким обра- зом, если оказывается затруднительным вообразить наблюдае- мую грань извне, чтобы узнать, что означает направление "про- тив часовой стрелки", вместо этого можно рассматривать дан- ную грань с "неправильного" направления при условии, что одновременно будет выбрано и "неправильное" направление об- хода, то есть по часовой стрелке. Можно высказать удивление, почему для программы требуется выбор направления обхода против часовой стрелки. Это используется только в алгоритме удаления невидимых линий, для которого будем использовать команду Я (она будет обсуждаться в следующем параграфе). Как только что было сказано, обозначение ориентации против часо- вой стрелки зависит от направления наблюдения интересующей грани: если грань рассматривается с неправильной стороны, то
1.4. ОТРЕЗКИ ПРЯМЫХ ЛИНИЙ, ГРАНИ И ОБЪЕКТЫ 29 ориентация "против часовой стрелки" становится ориентацией "по часовой стрелке" и наоборот. Этот факт используется в прог- рамме для быстрого определения, не будет ли заданная грань при рассмотрении из заданной точки наблюдения так называемой задней гранью. Если это так, то такая грань просто игнорируется в сравнительно сложном процессе удаления невидимых линий и что значительно ускоряет обработку. В нашем примере куба с точкой наблюдения, использованной на рис. 1.8 и 1.9, имеются три задних грани. (Фактически программа D3D использует толь- ко первые три вершины $ описании грани для определения, не будет ли она задней гранью. При этом предполагается, что номе- ром второй вершины должна обязательно обозначаться выпуклая вершина, о чем будет сказано в параграфе 1.7). Существует один случай, при котором порядок описания но- меров точек совершенно безразличен, а именно — когда сущест- вуют только две точки, как, например, в последовательности: F 2 8. которая в нашем случае соответствует диагонали, соединяющей две противоположные вершины куба. Таким образом, входная строка с номерами точно двух точек обозначает отрезок прямой линии между этими двумя точками. Если заданы точно три точ- ки, то они обозначают треугольник при условии, что эти точки не лежат на одной и той же прямой линии. Если заданы четыре и более точек, то может возникнуть целый ряд проблем, если точ- ки в действительности не являются вершинами полигона. Понят- но, что точки должны лежать в одной плоскости. Если нет, то на экране появится сообщение об ошибке: Не в одной плоскости Как и команда /, описанная в предыдущем параграфе, коман- да F должна быть задана только один раз: программа D3D пом- нит об этой команде до тех пор, пока вводятся перечни вершин, описывающих грани. Но после некоторых других команд потре- буется снова ввести команду F для возобновления ввода опи- саний граней. При таком способе назначение вводимой строки, начинающейся с номера вершины, если он является допусти- мым, всегда будет однозначным — данная строка принадлежит либо команде /, либо команде F. Если ввод номера недопустим,
30 Глава L ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ то первая же введенная цифра вызовет появление сообщения: Нельзя начинать цифрой Заметим, что после ввода последнего номера вершины в спис- ке вершин для грани должна быть обязательно введена точка (.). Это необходимо вследствие того, что грань может быть ограниче- на любым полигоном, а не только квадратом как в вышеприве- денном примере и такой полигон может описываться таким ко- личеством вершин, что их номера не поместятся в одной строке той части экрана, которая используется для этой цели. В этом случае придется вводить номера вершин в нескольких строках и необходим специальный сигнал для завершения последователь- ности, а поскольку символ перевода строки уже не может слу- жить этой цели, то применяется символ точки. При вводе номеров вершин граней можно сделать ошибку, приводящую в результате к вводу ненужной грани. Очевидно, что необходимо иметь средство для удаления такой только что введенной грани. Желательно иметь возможность удалять грани аналогично удалению точек с помощью команды D. Если уда- лить точку, использованную для описания грани, то при этом од- новременно будет удалена и эта грань. В принципе можно (хотя и косвенно) использовать удаление точки для удаления грани. Но нужно хорошо представлять себе, что удаление одной точки предполагает удаление всех граней (и соответствующих отрез- ков), для которых эта точка является вершиной, поэтому такой способ во многих случаях очень опасен: могут быть удалены и те грани, которые нужно сохранить. Поэтому для удаления граней предлагается другой способ — ввести команду L которая является аббревиатурой слова List ("Список"). По этой команде перечисляются поочередно одна за другой все грани в форме списка номеров вершин и для каждой грани запрашивает- ся подтверждение ОК? ("годится?"), как, например: 12 3 4. .- * OK?(Y/N): Если в ответ нажать на клавишу N ("Нет"), то грань с вер- шинами 12 3 4 будет удалена; при нажатии на клавишу Y ("Да") картинка останется без изменения. Затем в любом случае будет
1.4. ОТРЕЗКИ ПРЯМЫХ ЛИНИЙ, ГРАНИ И ОБЪЕКТЫ 31 выведен список вершин для следующей грани и так далее. Если нужно остановить этот процесс, то вместо У или N следует на- жать любую другую клавишу. После ввода данных по объекту, например, куба в нашем слу- чае, изображение этого объекта будет выведено на экран. Теперь можно удалить как номера вершин, так и оси (вместе с верти- кальными проецирующими линиями) командой смены режима м и нажатием клавиши N в ответ на запросы Номера точек? (Y/N): Och?(Y/N): (Фактически любой ответ отличающийся от У или у будет интер- претироваться как N). Если в ответ на последний запрос нажать клавишу У, то появится следующий вопрос: Вспом.линии?(У/ГМ): Этот вопрос относится к необходимости проведения вспомога- тельных линий, опускаемых из каждой заданной точки верти- кально на плоскость ху. Они очень полезны для получения ин- формации о местонахождении "свободной" точки в трехмерном пространстве, но нежелательны во многих других случаях, когда точки соединены отрезками прямых линий. Затем будут выведе- ны на экран следующие два вопроса Толстые линии? (Y/N): Весь экран? (Y/N): Если ответами на эти вопросы будут YnNсоответственно, то объект будет вычерчен так, как изображено на рис. 1.10. Теперь на чертеже нет ни номеров вершин, ни осей, ни дополнительных проецирующих линий. Как было упомянуто в параграфе 1.2, ребра объекта, расположенные на переднем плане, изображают- ся более толстыми линиями, чем ребра, лежащие на заднем пла- не. Это упрощает интерпретацию проволочной модели, по срав- нению с изображением с линиями равной толщины, что очень четко продемонстрировано на рис. 1.10. В некоторых других слу- чаях эффект различной толщины линий может проявляться не так явно. Если желательно изобразить все линии одинаковой толщины, то в ответ на первый вопрос о толщине линий необхо-
32 Глава 1. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ Г 1 Программа DX Аостдпныв номаяОы ! X/Y/Z Управл.курсором ? Значения координат М-режим (А-оси и т.е.) FidloMOUb) I Q-еыхоЭ - S-шаг 1 С-очистить 1-ввод 1 D-уэалить F-грани 1 L-список R-чтение I W-запись Е-весь экр1 Н-навиоимы V-т.зрения! Т-преобр. Вводите команду! Рис. 1.10. Куб димо нажать на клавишу TV. Наконец, для получения перспек- тивного изображения на всем экране (вместо только его левой части), в ответ на второй вопрос следует нажать на клавишу У. Действие аналогично применению команды £, которая будет описана ниже. Следует упомянуть, что "режим по умолчанию" сразу же после пуска программы на выполнение по команде D3D соответствует ответам У, У, У, У, N> именно в этом порядке, в виде реакции на пять перечисленных вопросов. Как только весь объект появится на экране, обычно применяется команда М для удаления номеров вершин, осей и дополнительных проецирую- щих линий. Режим по умолчанию несколько отличается от толь- ко что упомянутого, если полный объект считывается из файла по команде R, как это было описано в параграфе 1.2. Если только набор точек (без описания каких-либо граней или отрезков пря- мых линий) считывается из файла, по умолчанию номера точек и оси не изображаются, то есть команда R несколько изменяет режим по умолчанию. Чтобы сделать номера точек и оси види- мыми, можно использовать команду М.
1.5. ВЕСЬ ЭКРАН, НЕВИДИМЫЕ ЛИНИИ, ПЕЧАТЬ 33 1.5. ВЕСЬ ЭКРАН, НЕВИДИМЫЕ ЛИНИИ, ПЕЧАТЬ Теперь желательно отпечатать на матричном принтере пол- ученный графический результат. Но прежде чем это сделать, попробуем использовать весь экран для увеличенного перспек- тивного изображения и ликвидировать область сообщений. Это достигается вводом команды Е что означает Entire screen ("Весь экран"). Как уже было сказано в предыдущем параграфе, переключение на весь экран может быть выполнено командой изменения режима Mode (или, сокра- щенно, М)> которая более удобна, если одновременно желатель- но внести изменения в текущий способ отображения или исклю- чить отображение номеров точек, осей, дополнительных проеци- рующих линий и изменение толщины линий. Если не нужно изменять эти параметры (все вместе обозначаемым одним сло- вом mode — "режим"), то команда Е спасает от необходимости нажатия на клавиши У или N несколько раз. Что же касается переключения отображения картинки на весь экран, то обе команды, М и Е, совершенно эквивалентны. В любом случае изображение исчезает с экрана и предлагает- ся возможность использовать параметр aspect ratio С'отношение сторон") для получения наиболее приемлемого результата на принтере. Здесь имеется в виду соотношение между горизон- тальными и вертикальными размерами. Например, если на экра- не был получен квадрат, и при выводе на печать желательно по- лучить также квадрат, а не какой-то прямоугольник или парал- лелограмм, не являющийся квадратом, или окружность на экра- не также должна быть отпечатана в виде окружности, а не в виде эллипса. Поскольку программа осуществляет просто отображе- ние пикселов на экране в точки на матричном принтере, подсве- ченные пикселы на экране должны быть выбраны такими, чтобы отношение строк на принтере автоматически было правильным. Это будет реализовано в том случае, если после предыдущей ко- манды Е введена команда А (Сокращение от названия Aspect ratio — "отношение сторон"). После ввода команды А на экране может появиться несколько 2-273
34 Глава L ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ искаженное изображение. Но если будет включен матричный принтер и введена команда Р то на принтере появится картинка с точным соотношением раз- меров. Если предпочтительно точное соотношение размеров на экране (например, при отсутствии необходимости вывода каких- либо изображений на принтер), то вместо ввода команды А мож- но нажать на любую другую клавишу. Запомните — нельзя реа- лизовать оба способа одновременно: либо соотношение сторон будет правильным на экране, но неверным на принтере, либо оно будет точным на принтере, но искаженным на экране. Вместо команды А можно задать команду О. По этой команде описание изображения записывается в выходной файл, который первоначально предполагался для использования в качестве входного файла для другой программы — PLOTHP, чтобы полу- чить чертеж на плоттере фирмы Hewlett-Packard. Более подроб- но этот режим будет описан в параграфе 4.6. Это очень интерес- ная возможность, если доступен такой плоттер или иное графи- ческое устройство для "отложенного вывода". В этой книге мож- но обнаружить два вида иллюстраций: чертежи, содержащие не- сколько изломанные линии среднего качества, были получены на матричном принтере, тогда как чертежи с хорошими прямы- ми линиями были получены на плоттере фирмы Hewlett-Packard только что упомянутым способом. На данном этапе в качестве важного момента нужно упомянуть, что входные файлы для программы D3D и выходные файлы, полученные по команде О, имеют совершенно различный формат (хотя оба они записаны в виде файлов в формате ASCII). Во избежание путаницы, первый из них будем называть объектным файлом и имена для таких файлов снабжать расширением .DAT, тогда как последний будем называть чертежным файлом с расширением имени .PLT. После того, как картинка будет напечатана по команде Л или после нажатия клавиши Enter, если вывод на печать не нужен, большое перспективное изображение с экрана исчезнет и поя- вится точно такое же изображение, что было перед вводом ко- манды Е. В принципе, по команде Е увеличивается то изображение, которое до этого было видно на экране. Как было показано, это
1.5. ВЕСЬ ЭКРАН, НЕВИДИМЫЕ ЛИНИИ, ПЕЧАТЬ 35 утверждение может отказаться не совсем точным при новом со- отношении сторон, но оно применимо к номерам вершин и осям. Следовательно, если эти элементы входят в состав картинки, но они нежелательны в окончательном чертеже, то необходимо ис- пользовать команду М, вместо команды Е, как было сказано в параграфе 1.4. |Сроме изменения "режима" может отказаться желательным изменить точку наблюдения, для чего применяет- ся команда V, как было описано в параграфе 1.2. Впредь до сле- дующего изменения остается в действии самая последняя задан- ная позиция точки наблюдения. На рис. 1.11(a) и (б) показаны изображения на экране до и после команды Е, применяемой для одного и того же объекта (объемной буквы L), отображаемого в виде проволочной модели. В большинстве применений вместо проволочной модели предпочитают применять модель сплошного тела или, другими словами, требуется удаление невидимых линий. Программа D3D может обеспечить и такой режим. Когда объект изображен на эк- ране в виде проволочной модели, можно ввести команду н как аббревиатура для hidden-line removal (''удаление невидимых линий"). После чего на экране появится вопрос: Нужно ли, чтобы грани объекта аппроксимировались как поверхность? (Y/N): Ответим пока, временно, отрицательно, введя команду N Затем можно выбрать одну из двух опций, описанных ранее в связи с обсуждением команды Е: А рекомендуется в сочетании с командой Р для вывода на мат- ричный принтер или О для получения файла отложенного вывода на графопострои- ^ тель. Если ни одна из этих опций не нужна, то можно нажать на любую другую клавишу. После этого можно ввести команду Р для вывода на матричный принтер. За исключением вопроса о кривых поверхностях, команда Н совершенно аналогична ко- манде Е9 если рассматривать их опции. Ввод команды А говорит о том, что желательно скорректировать соотношение размеров при 2**
36 Глава I. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ ч Г) Программа DX Доступные команды '. Х/Ч/1 Управл.курсором ? Значения координат М-режим (А-оси и т.д.) Fl(noMOUb)! Q-выход S-шаг 1 С-очиститъ 1-ввод 1 D-yвалить F-грани I L-список R-чтвние 1 W-эались Е-весь экр1 Н-невидимы V-т.зрения! Т-преобр. Вводите команду! (а) (б) (в) Рис. 1.11. Тестовый объект: (а) — на рабочем экране; (б) — после команды Е; (в) — после команды Н
7.5. ВЕСЬ ЭКРАН, НЕВИДИМЫЕ ЛИНИИ, ПЕЧАТЬ 37 выводе на матричный принтер за счет искажения соотношений в картинке, изображаемой на экране, а после команды О в качест- ве побочного продукта будет получен графический файл для по- следующего вывода изображения на плоттер, если он будет до- ступен. Во всяком случае, будут выполнены все вычисления, необходимые для удаления невидимых линий. Для сложных объ- ектов (и при использовании дешевого компьютера с процессором 8088) может потребоваться значительное время на выполнение вычислений, прежде чем на экране появится желаемая картин- ка, поэтому нужно набраться терпения. Используя снова преды- дущий пример, получим рис. 1.11 (в). Отметим, что кроме ребер, которые либо полностью видны, либо полностью невидимы, есть и такие более интересные ребра, которые частично видимы и ча- стично невидимы. Команду изменения режима М после команды Н применять нельзя и результирующий чертеж с удаленными невидимыми линиями всегда занимает полный экран. В полу- ченном таким образом чертеже номера точек, оси и вспомога- тельные линии не отображаются, а все линии изображаются с одинаковой толщиной. После возврата к нормальному рабочему экрану снова появляется область сообщений и восстанавливается старый режим (относительно вывода номеров точек, осей, вспо- могательных линий и толщины вычерчиваемых линий). Чертеж рис. 1.11 (в) был получен на плоттере фирмы Hewlett- Packard с помощью опции О и программы PLOTHP (см. пара- граф 4.5), тогда как чертежи рис. 11 (а) и (б) были просто напеча- таны на матричном принтере (StarNLlO). Команда Р может быть использована не только после команд £ и Я, но также и непос- редственно при работе в режиме рабочего экрана, что демонстри- рует рис. 1.11 (а). Поскольку это не очень интересная опция, она не упомянута в меню команд. При выводе на плоттер программа PLOTHP не реализует вывод различной толщины линий. Этим можно объяснить некоторое очевидное различие в способе полу- чения большинства иллюстраций. С одной стороны, всегда жела- тельно получить наивысшее качество, что приводило к использо- ванию плоттера везде, где только было можно, а с другой сторо- ны иногда хотелось показать различную толщину линий или номера точек. Поэтому и было решено использовать плоттер в случаях, подобных рис. 1.11 (в), где даже при изображении на эк- ране все линии отображаются с одинаковой толщиной и отсутст-
38 Глава J. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ вуют номера точек, а в остальных случаях применять матрич- ный принтер. Напомним, что в зависимости от конкретного при- менения, совсем не обязательно всегда выводить картинку на плоттер, поскольку и такие чертежи, пример которого показан на рис. 1.11(b) могут быть с успехом получены на матричном принтере. Если побеспокоиться, чтобы на принтере была уста- новлена новая красящая лека, то качество полученного черте- жа будет вполне приемлемым для многих случаев. Вернемся к основной обсуждаемой теме — командам Ей Н и рассмотрим другой пример, в котором чертежи, показанные на рис. 1.12 (а) и (б), получены по этим командам соответственно. Объектом здесь будет додекаэдр, хорошо вписывающийся в куб. Данные для этого примера могут быть получены с помощью ко- манды R, кратко упомянутой в параграфе 1.1, для считывания из файла EXAMPLE2.DAT на программном диске. Додекаэдр имеет двенадцать граней, в виде правильных пятиугольников, их фор- мирование будет описано более подробно в параграфе 3.5.4. На рис. 1.12(a) объект показан в виде проволочной модели, тогда как на рис. 1.12(6) додекаэдр считается непрозрачным те- лом. Ребра куба изображаются в виде "свободных" отрезков пря- мых линий. Как указывалось в параграфе 1.4, отрезки прямых линий могут быть введены просто поочередно по команде F как грани только с двумя вершинами. Таким образом куб представ- ляется в виде проволочной модели, без граней, которые могут за- крывать другие отрезки. С другой стороны, ребра куба могут за- крываться гранями додекаэдра, как видно на рис. 1.12 (б). И здесь наиболее интересный случай, когда ребра частично видимы и ча- стично невидимы; эта ситуация четко указывает на то, что име- ем дело с трехмерным объектом и что вычисления, необходимые для получения результата, оправдывают их стоимость. Прово- лочное представление додекаэдра на рис. 1.12(a) выглядит менее эстетично, чем рис. 1.12(6), но оно тем не менее достаточно яс- ное благодаря различию в толщине линий. Вернемся к вопросу о криволинейных поверхностях, который появляется на экране сразу же после ввода команды Я. Для само- го простейшего объяснения рассмотрим рис. 1.13 (а), (6) и (в), которые могут быть получены по данным из файла EXAMPLE3.DAT на программном диске. В параграфе 3.11 будет показано, как можно создать такой файл.
1.5. ВЕСЬ ЭКРАН, НЕВИДИМЫЕ ЛИНИИ, ПЕЧАТЬ 39 Рис. 1.12. Додекаэдр в кубической решетке: (а) — проволочная модель; (б) — сплошное тело
40 Глава I. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ В этом Т-образном объекте предполагается, что он составлен из двух цилиндров. В действительности наша программа воспри- нимает только такие тела, которые в качестве граней имеют пло- ские фигуры, но путем использования большого числа плоских граней можно аппроксимировать и кривые поверхности. Так на рис. 1.13 (а) изображены все плоские ограничивающие грани (если только они видимы). Здесь все видимые линии пересечения представляются вычерченными отрезками прямых линий. Боль- шинство из них относится к линиям на цилиндре, параллельным оси цилиндра. Они возникают только в процессе аппроксимации и не существуют как линии пересечения на реальных цилинд- рах. На рис. 1.13(b) эти линии опущены. Такой результат можно получить, ответив нажатием на клавишу У в ответ на вопрос: Нужно ли, чтобы грани объекта аппроксимировались как поверхность? (Y/N): что означает желание получить "гладкую" аппроксимирован- ную кривую поверхность. Затем на следующий запрос вводится подходящее значение "порогового" угла а. Рис. 1.14 поясняет смысл этого угла. Значение угла а будет использовано для ана- лиза относительного расположения двух соседних граней, или, более точно, двух векторов нормалей к этим граням. Если угол между векторами будет меньше или равен а, то ребро между гра- нями вычерчиваться не будет; если вычисленное значение угла превышает а, то ребро (если только оно видимо) будет вычерче- но. Таким образом, значение а используется для принятия реше- ния о том, какое из (видимых) ребер должно вычерчиваться и какое нет. Увеличение значения а может уменьшить количество вычерчиваемых ребер. Если а = 0°, то будут вычерчены все ребра таким же образом, как если бы в ответ на предыдущий вопрос о кривых поверхностях была нажата клавиша N. Заметим, что по- роговое значение угла а применяется только для видимых линий и оно никак не влияет на удаление невидимых линий: скрытые линии никогда не будут вычерчиваться, независимо от значения угла а. Конечно, ненулевое значение а может сильно повлиять на время вычислений, поскольку оно может уменьшать количес- тво ребер, являющихся кандидатами на вычерчивание, и, следо- вательно, в свою очередь уменьшить объем работы на вычисле- ния по удалению невидимых линий. Данные в файле
1.5. ВЕСЬ ЭКРАН, НЕВИДИМЫЕ ЛИНИИ, ПЕЧАТЬ 41 Рис. 1.13. Пересекающиеся цилиндры (а) а - 0°, (б) а - 25°, (в) а - JJ°
42 Глава У. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ Рис. 1.14. Пороговый угол а EXAMPLE3.DAT можно использовать для эксперимента с раз- личными значениями угла а. При задании а = 25° параллельные ребра малого цилиндра будут вычерчены, тогда как такие же ре- бра для большого цилиндра будут опущены, что показано на рис. 1.13(6). При угле а = 35° параллельные ребра не вычерчиваются на обоих цилиндрах, это видно на рис. 1.13(b). Случайно именно такое пороговое значение устанавливается по умолчанию, поэ- тому вместо ввода числа можно просто нажать на клавишу ввода. 1.6. ОБЪЕКТНЫЕ ФАЙЛЫ В параграфе 1.1. мы видели, что команду R можно использовать для чтения такого файла, как, например, файл EXAMPLE1.DAT, Поскольку запуск программы D3D на выполнение очень часто комбинируется совместно с командой R, то для достижения нужного результата есть более удобное сред- ство — имя файла, подлежащего считыванию, можно записать совместно с командой вызова программы: D3DEXAMPLE1.DAT В этом случае нет необходимости в команде R сразу же после запуска программы. Если в имеющемся компьютере установлен процессор 8088 (что имело место у автора книги), то различие в
7.6. ОБЪЕКТНЫЕ ФАЙЛЫ 43 толщине линий значительно замедлит процесс вычерчивания изображения на экране монитора. Это может несколько раздра- жать, если из-за сложности объекта изображается очень большое количество отрезков, сформированных на основе входного фай- ла. Если имя входного файла задается при запуске программы D3D, то дается возможность отображения всех линий на экране с одинаковой толщиной. Для этого необходимо нажать на клави- шу N и в ответ на вопрос, который появляется на экране: Желательна ли разная толщина линий по глубине? (Y/N): Этот запрос аналогичен вопросу о "толстых линиях" после команды М, как упоминалось в параграфе 1.4. Такой новый способ определения входного файла не исклю- чает необходимости в команде Л, поскольку она может потребо- ваться в тех случаях, когда на экране уже есть изображение. На экране появится следующий вопрос: Очистить экран?(У/1М) — Чтобы понять смысл этого запроса, необходимо уяснить, что мо- жет потребоваться любая из двух возможностей: 1. Желательно очистить экран, чтобы новый объект, который будет считан из файла, заменил старый. В этом случае сле- дует нажать на клавишу У. 2. Желательно сохранить старый объект видимым на экране и его нужно дополнить тем, что будет считано из файла. Лю- бой конфликт между номерами точек должен быть разрешен путем перенумерации вновь считываемых точек системати- ческим образом, чтобы фактически сконструировать новый составной объект. Для этой опции на предыдущий вопрос следует ответить нажатием на клавишу N. Последняя опция предоставляет средство для использования библиотеки с определенными объектами, которые часто жела- тельно использовать в комбинации с другими, хотя это не будет действительно полезным до тех пор пока мы не знакомы с преоб- разованиями, которые будут описаны в параграфе 1.8. Если было сконструировано перспективное изображение объекта, то этот объект может быть сохранен в памяти путем записи его в объектный файл, который имеет точно такую же структуру, как и входной файл. Тогда файл, записанный таким
44 Глава 1. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ образом, может быть впоследствии прочитан командой R. Теку- щий объект (видимый на экране) может быть записан в объект- ный файл с помощью команды W Рассмотрим еще раз объектный файл EXAMPLE1.DAT для обсуждения структуры того, что мы называем объектным фай- лом. На рис. 1.15 этот объект изображен с номерами точек. Оси не показаны, поскольку их очень легко представить: точка 1 рас- положена в начале системы координат, а точки 2, 7 и 6 лежат на осях х, у, z соответственно. Если программа D3D еще не запускалась на выполнение, то содержимое файла EXAMPLE 1.DAT можно посмотреть различ- ными способами, например, вводом команды: TYPEEXAMPLE1.DAT или TYPE EXAMPLE1.DAT >LPT1 если желательна распечатка данных. Тогда получим список чи- сел, показанный в таблице 1.1. Рис. L15. Объект и номера вершин
1.6. ОБЪЕКТНЫЕ ФАЙЛЫ 45 Таблица 1.1 Содержимое файла EXAMPLE1.DAT 1 0.000000 0.000000 0.000000 2 3.000000 0.000000 0.000000 3 2.000000 0.000000 1.000000 4 1.000000 0.000000 1.000000 5 1.000000 0.000000 2.000000 6 0.000000 0.000000 2.000000 7 0.000000 1.000000 0.000000 8 3.000000 1.000000 0.000000 9 2.000000 1.000000 1.000000 10 1.000000 1.000000 1.000000 11 1.000000 1.000000 2.000000 12 0.000000 1.000000 2.000000 Faces - Грани: 12 3 4 5 6. 7 12 11 10 9 8. 2 8 9 3. 3 9 10 4. 4 10 11 5. 5 11 12 6. 1 б 12 7. 2 1 .7 8. ^ Если вы читали предыдущую книгу автора "Принципы про- граммирования в машинной графике", то можете заметить, что этот файл имеет такую же структуру, как аналогичные файлы в той книге, за исключением его начала: в предыдущей книге объ- ектные файлы начинались с трех координат более или менее центральной точки объекта. Теперь это не нужно (и фактически даже не должно появляться), поскольку программа сама может вычислить эти координаты (во входных файлах для программы D3D может быть использован и символ #, употреблявшийся в предыдущей книге, если он более предпочтителен, чем точка, применяемая в данной книге). Посмотрим теперь, как объект- ный файл описывает объект. Таблица 1.1 начинается с двенадца- ти строк, каждая из которых содержит четыре числа: положи- тельное целое число, определяющее номер вершины, как изо- бражено на рис. 1.15, и ее три координаты jc, у, z. После этого описания всех точек следует вторая часть файла, начинающаяся со строки: Faces - Грани:
46 Глава 1. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ Каждая ограничивающая грань теперь определяется списком номеров вершин, перечисляемых в направлении обхода против часовой стрелки при наблюдении этой грани извне объекта; за номером последней вершины немедленно следует точка. Ограни- чивающая грань может определяться полигоном с таким числом вершин, что список номеров может не поместиться в одной текс- товой строке. Поэтому нельзя использовать символ конца строки в качестве сигнала о номере последней вершины. Теперь может оказаться понятным использование некоторого специального символа (точки). Если этому символу предшествуют только два номера вершин, то этим обозначается отрезок прямой линии. 1.7. ВОГНУТЫЕ ВЕРШИНЫ, ОТВЕРСТИЯ В большинстве случаев внутренний угол при вершине поли- гона составляет меньше 180°. Такие вершины будем называть выпуклыми. Говорят, что если все вершины полигона выпуклые, то и сам полигон выпуклый. Вершина называется вогнутой, если внутренний угол при ней больше 180°. Например, такой случай имеет место при вершине 2 на рис. 1.16(a), которая находится на передней грани сплошного тела в форме буквы V, показанной на рис. 1.16(6). Как известно, ограничивающая грань объекта за- дается в виде последовательности номеров вершин в порядке, соответствующем обходу всех вершин против часовой стрелки. Программа D3D (или, более точно, команда Н) использует ориентацию первых трех вершин в такой последовательности для отличия потенциально видимых граней от задних граней. Например, полигон на рис. 1.16(a) можно задать списком 316524. Поскольку первые три вершины обходятся против часовой стрелки, то этот полигон потенциально видимый. Однако было бы ошибочным задать последовательность в виде 524316. Тогда номера первых трех вершин (5, 2, 4) соответствовали бы обходу по часовой стрелке и это приведет к ошибочному ре- шению внутри программы D3D, что этот полигон определяет заднюю грань. Источник затруднений здесь заключен в неудач- ном указании вогнутой вершины (с номером 2) во второй пози-
1.7. ВОГНУТЫЕ ВЕРШИНЫ, ОТВЕРСТИЯ 47 (а) (б) Рис. 1.16. (а) - полигон с вогнутой вершиной; (б) - результат выполнения команды Н ции последовательности. Поэтому необходимо запретить такую ситуацию и потребовать, чтобы номер второй вершины соответ- ствовал выпуклой вершине. . До сих пор мы имели дело с правильными полигонами, то есть предполагали, что вся внутренняя область каждого полигона образует грань объекта. Теперь проанализируем полигоны с от- верстиями, для описания которых будем применять специаль- ную методику. Рассмотрим для примера прямоугольник с двумя отверстиями, показанными на рис. 1.17. Поскольку это не чис- тый полигон, преобразуем его в правильный путем добавления двух новых ребер, а именно 3-11 и 4-8, как показано штриховы- ми линиями на рис. 1.17 Теперь можно доказать утверждение, что последовательность номеров 123 11 10 9 12 11 34876584 определяет полигон, в котором два ребра 3-11 и 11-3 совпадают (это же относится и к ребрам 4-8 и 8-4). Однако, эти штриховые
48 Глава 1. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ Рис. 1.17. Полигон с отверстиями ребра не должны вычерчиваться и тем или иным способом нужно отметить, что эти две пары совпадающих ребер в нашем примере являются чисто искусственными. Этого можно достичь путем со- глашения, что вместо пары ребер PQ будем задавать пару P-Q если PQ является таким искусственным ребром. Знак минус предотвратит вычерчивание отрезка PQ. Поэтому в нашем при- мере на рис. 1.17 в объектный файл можно включить строку 12 3-11109 12 11-34-87 658-4. вместо приведенной выше последовательности положительных чисел. Заметим, что если обходить все вершины на рис. 1.17 в этом порядке и всегда глядеть на следующую вершину, то в про- цессе обхода "тело" описываемого полигона будет находиться слева по движению. Для простого выпуклого полигона, напри- мер, прямоугольника, это означает, что обход осуществляется против часовой стрелки, но при отслеживании отверстия верши- ны обходятся в направлении по часовой стрелке. Этим объяс- няется, например, порядок обхода в подпоследовательности 1091211 которую нельзя заменить на 1291011
1.7. ВОГНУТЫЕ ВЕРШИНЫ, ОТВЕРСТИЯ 49 Структуре объектных файлов уделено много внимания по двум причинам. Во-первых, объектные файлы могут генериро- ваться другими программами, как это будет показано в главах 2 и 3, и, кроме использования описанных здесь готовых к примене- нию программ, читатель может захотеть самостоятельно напи- сать подобные программы. Во-вторых, понимая теперь как по- лигоны с отверстиями представляются последовательностями номеров, мы можем дополнить нашу дискуссию о вводе граней в параграфе 1.4. После команды .Рмы можем использовать тот же способ и для отрицательных номеров точек. Например предполо- жим, что рис. 1.17 представляет собой вид сверху призмы с двумя сквозными отверстиями. Нижняя грань призмы аналогична вер- хней грани. Будем считать, что номера вершин для нижней гра- ни получены путем увеличения на 12 номеров соответствующих вершин верхней грани. Этот объект в различных проекциях по- казан на рис. 1.18(а)-(г). Рис. 1.18.Призма с отверстиями: (а) — <р-80°, проволочная модель; (б) — <р - 80°, сплошное тело
50 Гдава 1. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ Рис. 1.18.Призма с отверстиями {продолжение): (в) — у>« 10°, проволочная модель; (г) —<р- 10°, сплошное тело
1.7. ВОГНУТЫЕ ВЕРШИНЫ, ОТВЕРСТИЯ 51 Приведем теперь полный перечень нажатий клавиш и все данные, которые должны быть введены для того, чтобы описать такой объект и сформировать его изображения. Все точки будут вводиться с помощью курсорных клавиш со стрелками. Нажатие клавиши Enter будем обозначать словом "Ввод"; пробелы между цифрами должны вводиться обязательно, а все другие пробелы — не обязательно. Комментарии, в действительности не вводи- мые, даются внутри фигурных скобок { }. Напомним, что можно использовать строчные буквы вместо прописных. Практически приводимый ниже список выглядит значительно сложнее, чем он есть на самом деле, поскольку здесь не показаны сообщения, ото- бражаемые на экране в виде подсказки в процессе ввода данных. Так, например, строка HNAP которая встречается ниже дважды, может показаться ужасной, но программа подсказывает, какие нужно ввести буквы. Буква Н является первой буквой слова Hidden {"'невидимые"), которое можно видеть в главном меню команд. Если нажать эту клави- шу, то на экране появится вопрос Нужно ли, чтобы грани объекта аппроксимировались как поверхность? (Y/N): на что следует ответить нажатием на клавишу N, то есть ввести вторую букву из последовательности HNAP. На экране появится текст: После отображения результата на экране его можно напечатать на матричном принтере путем ввода команды Р. Если вместо этого будет нажата любая другая клавиша, то на экране восстановится предыдущее изображение. Будем говорить, что используется точное отношение сторон, если окружность изображается как окружность, а не эллипс. Нормально отношение сторон будет верным на экране. Если же верное отношение сторон нужно обеспечить на принтере (при допускаемом неверном отношении на экране), то, пожалуйста, введите команду А. Можно ввести команду О, если необходимо получить выходной файл (xxx.PLT), который затем может быть, например, прочитан программой PLOTHP для формирования изображе- ния на плоттере фирмы Хьюлетт Паккард или другом графопостроителе. Если ничего этого не нужно, то нажмите любую другую клавишу... Этим полностью объясняется назначение третьей и четвертой букв в последовательности HNAP. Как только введена команда
52 Глава 1. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ D3D для запуска программы, использование большинства ос- тальных строк, приведенных ниже, оказывается очень простым из-за пояснительного текста на экране. D3D<Bboa> {Начало выполнения программы} <Ввод> V 10000 <Ввод>0<Ввод>0<Ввод> {Вид сверху р- 10000,0-0*,^ -0*} Z+++ {Переход к верхней грани} Х-т-н-l {точка 1} Yi i i i i i i i i И {точка2} X 1 {точка 3} Y 1 {точка 4} X-H++Y+I {точка 5} +++I {точка 6} X-I {точка 7} Y 1 {точка 8} 11111 i X+I {точка 9} Y+I {точка 10} X-I {точка 11} Y-I {точка 12} F {Описание верхней грани} 12 3-11 109 12<Ввод> 11-34-8 7 6<Ввод> 58-4.<Ввод> \/<Ввод><Ввод>180<Ввод> {Вид снизу:р- 10000.0-О\^>,- 180'} {Далее выполним точно такие же операции по вводу, как и выше, начиная с Xniiiil и кончая буквой F, каждый номер точки j соответствует ниже номеру точки J +12:} Xi ■ i i H {точка 13} Yi i i i i i i i i И {точка 14} X 1 {точка 15} Y 1 {Точка 16} X+++Y+I {точка 17} -и-ц {точка 18} X-I {точка 19} Y 1 {точка 20} i i i i 11X+I {точка 21} Y+I {точка 22} X-I {точка 23} Y-I {точка 24} F {описание нижней грани:} 161514-22 23 24<Ввод> 2122-14 13-17<Ввод> 1819 2017-13.<Ввод> \/8<Ввод>20<Ввод>80<Ввод> {Перспективар - 8, 0 - 20е, <р - 80*} F {Описание всех вертикальных граней}
1.7. ВОГНУТЫЕ ВЕРШИНЫ, ОТВЕРСТИЯ 53 13 14 2 1<Ввод> {Внешние грани} 14153 2<Ввод> 15164 3<Ввод> 161314<Ввод> 1817 5 6<Ввод> {Гранив левой дырке} 17208 5<Ввод> 20 19 7 8<Ввод> 19 18 6 7<Ввод> 22 21 9 1СКВвод> {Грани в правой дырке} 212412 9<Ввод> 242311 12<Ввод> 2322 1011<Ввод> MYNYY {Режим; Номера точек? Да. Оси? Нет. Толстые линии? Да Весь экран? Да.} АР {Отношение сторон. Печать рис. 1.18(a)} HNAP {Скрытые линии, Нет кривых поверхностей, Отношение сторон, Печать рис. 1.18(6)} MNNYN {Режим, Нет номеров точек, Нет осей, Толстые линии, Не весь экран} \/20<Ввод>20<Ввод> 10<Ввод> {Перспектива, р - 20, в - 20е, <р - 10*} Е АР {Печать рис. 1.18(в)} HNAP {Печать рис. 1.18(г)} W {Сохранение объекта} HOLES.DAT<Bboa> {Имя файла для записи} Q {Выход} Без сомнения, наиболее трудной частью при таком вводе является описание граней, поскольку необходимо строго соблю- дать ориентацию при перечислении вершин полигонов. Сущест- вует несколько специальных приемов для упрощения этой рабо- ты и поскольку программа D3D при этом станет более удобной в применении, обсудим ниже эти приемы более детально: 1. Для сложных полигонов, особенно если в них имеются от- верстия, как в верхней и нижней гранях нашего тестового объекта, может оказаться целесообразным изменить поло- жение точки наблюдения таким образом, чтобы смотреть на грань с внешней стороны объекта. Например, при описании нижней грани нашего объекта можно поместить точку на- блюдения под объектом, задав угол <р = 180°. 2. Настойчиво рекомендуется выбирать систематический по- рядок обхода как для самих граней, так и для вершин в каж- дой грани. В нашем примере имеем три набора из четырех прямоугольников и для каждого из них обход начинается с ближайшего ребра на нижней плоскости.
54 Глава 1. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ 3. Иногда номера вершин на экране становятся неразборчивы- ми из-за наложения друг на друга. В таких ситуациях может оказаться полезной команда Е, по которой картинка увели- чивается на весь экран, что может помочь выявить номера точек. Также иногда может оказаться полезным сделать распечатку картинки или, если нет возражений против ка- рандаша и бумаги, то зарисовать в виде грубого эскиза по- лученную к данному моменту картинку, включая номера точек. Нажатие на любую клавишу после команды Е вызо- вет- повторное появление уменьшенного изображения в ра- бочей части экрана. Если случайно нажатая клавиша ока- жется соответствующей букве Р, то ^еред исчезновением увеличенной картинки она будет напечатана на принтере. 4. Если необходимо проверить или скорректировать введенные грани, то можно использовать команду L, как было описано в параграфе 1.4. Это особенно важно, если при вводе была допущена ошибка, которая может испортить всю предыду- щую работу из-за случайного влияния на какую-либо грань, где она совершенно не нужна. По команде L отображается список всех граней и представляется возможность удаления неправильной грани просто нажатием на клавишу N ("Нет") в ответ на вопрос O.K.! (Y/N) ("Правильно?"). 5. Вместо ручного определения нескольких подобных граней можно описать только одну и вычислить остальные с по- мощью преобразований, описываемых ниже. 1.8. ПРЕОБРАЗОВАНИЯ В этом параграфе рассматриваются хорошо развитые преоб- . разования в трехмерном пространстве. В программе D3D можно выполнять операции поворота, переноса, масштабирования и зеркального отражения. С помощью каждого из этих четырех преобразований можно либо переносить, либо копировать трех- мерный объект, видимый на экране. (Кроме перемещения или копирования объекта целиком преобразования могут прилагать- ся к некоторой его части, ради простоты изложения пока эту воз- можность проигнорируем). Если мы будем перемещать объект, то появится его новая версия и в тот же момент старая версия будет удалена. В этом случае используемые номера вершин не
1.8. ПРЕОБРАЗОВАНИЯ 55 изменятся, поскольку вершины нового объекта будут иметь те же самые номера, какие были присвоены оригиналу. С другой стороны, если объект копируется, то старый объект остается на месте и он не заменяется, а просто дублируется. После этой опе- рации количество вершин удвоится по сравнению с исходным состоянием. Для любого преобразования нужно ввести команду т После чего на экране появится вопрос: Перенос (М) или копия (С) ? Этот вопрос следует понимать так: В результате преобразования существующий объект должен быть заменен или объект будет сдублирован? Очевидно, что нужно ответить нажатием на клавишу М, если желательна замена, или на клавишу С, если нужно дублирова- ние. (Различие между этими двумя опциями совершенно такое же, как между аналогичными операциями для перемещения и копирования частей текста в программах текстовых процессоров или текстовых редакторов, если читатель знаком с ними). В лю- бом случае появится строка сообщения: Нижняя граница: 1 в котором цифра ^ мерцает. Обычно это означает, что можно просто нажать на клавишу "Ввод" для принятия значения 1. Вместо этого можно ввести некоторое другое значение нижней границы, допустим, L. Затем появится новая строка: Верхняя граница:... означающая вопрос о верхней границе, где на месте многоточия (...) высвечивается наибольший используемый в данный момент номер вершины. Опять нажатием на клавишу "Ввод" можно принять это значение или задать иное значение для верхней гра- ницы, допустим, U. В этом случае последующие преобразования будут применяться ко всем вершинам, номера которых удовлет- воряют неравенству: L < i: < U Таким образом, если была дважды нажата клавиша "Ввод", то есть были приняты предложенные значения L и U, то будет
56 Глава L ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ преобразован весь объект целиком. Если при преобразованиях объект дублируется полностью или в некоторой его части, то на экране будет отображен диапазон вновь назначенных номеров вершин. Например, если вершины исходного объекта имели но- мера 1,2, ..., 10 и все вершины участвуют в процессе дублирова- ния (или "копирования"), тогда на экране в области сообщений появятся следующие три строки: Диапазон новых точек: 11-20 Нажмите клавишу... в которых содержатся данные о диапазоне номеров новых точек Ч1 Г—20 в этом примере). Это очень полезная информацию, по- скольку часто нужно выполнить не одно действие, а применить к некоторой части объекта целый ряд преобразований, например, перенос, поворот и масштабирование. Полезно отметить для себя этот диапазон, он может потребоваться позднее для ввода значе- ний нижней L и верхней U границ для другого преобразования. Конечно, номера вершин можно высветить и на картинке с по- мощью команды Му но для сложных объектов это вскоре вызовет взаимное наложение номеров и будет просто невозможно их про- читать. Поэтому лучше применить команду М для стирания но- меров вершин на картинке и использовать номера, высвечивае- мые в области сообщений. Для предотвращения затирания ото- бражаемого диапазона другими сообщениями при продолжении работы программы необходимо нажать любую клавишу. Описание сообщения о вновь формируемых вершинах было включено на этом этапе потому, что этот аспект является общим для всех четырех типов преобразований. В действительности это сообщение появится только после того, как все указанные вер- шины будут преобразованы, так что теперь настало время обсу- дить сами преобразования. После задания нижней и верхней гра- ниц номеров вершин на экране последовательно появится один или несколько из следующих вопросов: Поворот? (Y/N) Перенос? (Y/N) Масштаб? (Y/N) Отражение? (Y/N) Нажатием на клавишу Y может быть выбрано одно из этих преобразований. Следующий вопрос появится только в случае
1.8. ПРЕОБРАЗОВАНИЯ 57 иного ответа. И только если в ответ на последний вопрос "Отра- жение?", будет нажата клавиша N, тогда не будет определено никакого преобразования и будет произведен возврат на основ- ной уровень команд программы D3D. Более детально эти четыре преобразования будут описаны в параграфах с 1.8.1 по 1.8.4. Для каждого их четырех обсуждаемых типов преобразований запрашивается ввод одного или нескольких номеров вершин, чтобы задать информацию о выполняемых преобразованиях. Здесь допускается использовать целое число 0 для обозначения точки начала системы координат. Эта возможность в последую- щих параграфах явно не упоминается. Например, если в пара- графе 1.8.1 говорится, что перед вводом команды Т необходимо обязательно определить две точки Р и Q, то практически можно задать только одну, если в качестве другой может быть использо- вана 0. Заметим, что указание целого числа 0 не вызовет ника- ких недоразумений, поскольку все определенные пользователем точки имеют номера больше 0. Ради полноты необходимо обсудить еще одно средство, кото- рое рано или поздно может оказаться полезным и которое приме- нимо для всех четырех типов преобразований. Оно относится к номерам вершин, которые будут вводиться при задании деталь- ной информации о некоторых преобразованиях. Если эти номера вводятся нормальным образом, то есть в виде положительных чисел, то они принимают участие в процессе преобразования. Если же их нужно исключить и не подвергать преобразованиям (хотя они могут находиться в описанном выше заданном диапа- зоне от L до U), тогда их можно ввести с предшествующим зна- ком минус, например, -25 вместо 25. Хотя в последующем в ка- честве номера вершины используется абсолютное значение вве- денного числа, знак минус будет учтен в момент выполнения преобразования и такая точка будет пропущена, как если бы она находилась вне заданного диапазона L - U. Заметим, что это свойство реализуется только для некоторых специальных точек, например, для двух заданных точек, которые определяют ось для поворота, что будет описано в параграфе 1.8.1. 1.8.1. Поворот Поворот в трехмерном пространстве выполняется вокруг оси, на которой должны быть заданы две точки, обозначим эти точки
58 Глава L ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ Р и Q. Чтобы можно было определить ориентацию поворота, бу- дем рассматривать PQ как направленный отрезок прямой линии. Точки Р и Q могут быть выбраны в любом месте трехмерного пространства, то есть ось вращения совсем не обязательно долж- на быть горизонтальной или вертикальной. Нужно также задать угол поворота а. Тогда поворот будет осуществляться вокруг оси PQ на угол а таким образом, что перемещение от точки Р к точке Q будет соответствовать движению правого винта при его враще- нии в смысле заданного поворота. Точки Р и Q могут быть вершинами самого объекта, но в боль- шинстве применений это не то, что желательно. Поэтому обычно необходимо задать две точки Р и Q перед вводом команды Т. Но на практике это не вызывает особых затруднений, если забыли это сделать, так как всегда можно просто ответить нажатием на клавищу N в ответ на все вопросы или, если уже сказали, что ну- жен поворот, то можно выполнить поворот вокруг любой оси на угол 0°. В случае такого "фальстарта" происходит автоматиче- ский возврат в главное меню. После этого можно определить точ- ки Р и Q либо непосредственно командой /, либо с помощью уп- равления курсором, и затем снова задать команду 7\ Используя объект, показанный на рис. 1.19, можно получить рис. 1.20путем "копирования" объекта при повороте на угол 90° вокруг вертикальной оси, проходящей через точки 15 и 16, изо- браженные на рис. 1.19. Предположим, что была введена команда Т (Transform — "преобразование") и ответом на вопрос "Перенос (М) или копия (С)" было нажатие на клавишу С ("Копия"). Затем дважды нажимаем на клавишу Enter — "Ввод", чтобы подтвердить, что преобразованию подвергаются все точки. После выбора опера- ции поворота, как указывалось выше, на экране появятся две строки: Поворот вокруг PQ. Номера точек Р и Q; В нашем примере введем числа 1516 и нажмем клавишу "Ввод". Затем появится запрос на ввод угла: Угол в градусах:
1.8. ПРЕОБРАЗОВАНИЯ 59 Рис. 1.19. Объект, подлежащий.повороту, и ось поворота Если теперь ввести 90 то будет сформирована копия исходного объекта, повернутая на угол 90° вокруг заданной оси. После последующего удаления не- видимых линий (с помощью команды Я), получим результат рис. 1.20. Рис. 1.20. Результат поворота
60 Глава I. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ Заметим, что точки Р и Q трансформируются сами в себя, по- этому можно записать P'sP Q'SQ Будем говорить, что точки Р и Q (и фактически все точки на прямой линии PQ) являются фиксированными точками. Теперь предположим, что в ответ на вопрос "Перенос (М) или копия (С)" была нажата клавиша С. Поскольку точка Р' совпадает с точкой Р, то совершенно нет необходимости приписывать ей но- вый номер вершины. То же самое относится к точкам Q и Q'. Обычно желательно, чтобы этим точкам были приписаны но- вые номера вершин, если они являются вершинами объекта. Тог- да нам нужно чтобы исходный объект и его новая копия имели свои собственные наборы номеров вершин, которые потом можно задавать в качестве нового диапазона номеров вершин для после- дующих преобразований, выполняемых только над новой ко- пией. Однако, на рис. 1.19 точки 15 и 16 не принадлежат объекту и в подобных случаях для нас нежелательно приписывать новые номера вершин для этих фиксированных точек. Следовательно, при определении новых номеров вершин в течение процесса копирования, программа D3D будет делать исключение для вер- шин Р и Q, если номерам этих вершин предшествует знак минус при их вводе. В нашем примере для получения такого результата можно было бы ввести -15-16 (Но заметим, что в данном примере существует и другой способ достижения такого же результата, а именно: заданием номера 14 в качестве верхнего предела для всех номеров вершин, подверга- ющихся преобразованию, вместо простого нажатия на клавишу "Ввод", как было описано выше). / 1.8.2. Перенос Говорят, что выполняется операция переноса, когда объект просто перемещается в пространстве без изменения его ориента- ции. Выражаясь более точно, применяются три константы delta
1.8. ПРЕОБРАЗОВАНИЯ 61 х, delta у, delta z для вычисления х' = jc + delta х У = у + delta у z' = z + delta z для любой точки P(jc, у у z) объекта, чтобы найти соответствую- щую новую точку Р'(дс\ у, z'). В программе D3D можно ввести эти три значения delta jc, delta у, delta z явно, как три числовые константы или использо- вать вектор переноса АВ. В качестве вектора переноса может быть использован любой направленный отрезок АВ. После отве- та У в ответ на запрос "Перенос! (Y/N)" на экране отображается следующая строка: Вектор? (Y/N) Необходимо нажать на клавишу N, если вы намерены ввести три константы delta x, delta у, delta z; либо на клавишу У, если дол- жен быть использован некоторый вектор переноса АВ. В первом случае требуется ввести эти три константы, а во втором на экра- не появятся две строки запроса вектора АВ — вектор переноса Номера точек А и В: здесь мы должны ввести номера предварительно определенных двух точек. (Напомним, что это аналогично определению опера- ции поворота, где нужно было заранее определить точки Р и Q). Хотя здесь вводятся только номера точек, их координаты хА и так далее, уже известны и эти координаты определяют три кон- станты delta xy delta у, delta z следующим образом: delta х = дсв - хк deltay = yB-yA delta z = zB~zA На рис. 1.21 показан пример переноса. Он также основан на исходном объекте, показанном на рис. 1.19; вершины 16 и 15, именно в таком порядке, были использованы в качестве вектора переноса PQ. Если объект копируется (а не "переносится"), то сами точки А и В, хотя возможно и лежащие в диапазоне номеров вершин,
62 Глава 1. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ Рис. 1.21. Результат переноса подлежащих преобразованию, не копируются, если их номера были введены со знаком минус. 1.8.3. Масштабирование Масштабирование объекта означает изменение его размеров. Очень часто нам нужно сформировать новый объект, который во многом подобен старому, то есть при изменении общих размеров их пропорции должны остаться без изменения. В таких случаях применяется равномерное масштабирование. Вещественное чис- ло S, на которое умножаются все размеры, называется коэффи- циентом масштабирования. Обобщением этого случая будет задание различных коэффициентов масштабирования 5Х, S , Sz по направлениям всех трех координатных осей X, Y, Z. Очевид- но, что равномерное масштабирование будет частным случаем при ' S =S =S =S х у z
L8. ПРЕОБРАЗОВАНИЯ 63 Прежде чем выполнять операцию масштабирования, нам не- обходимо знать фиксированную точку, которая остается неиз- менной после преобразования масштабирования. Если для этой цели используется точка 0 начала системы координат, то тогда для любой точки РОс, у, z) определяется новая точка РОс', у', z') на основании уравнений x'mSx ' х y' = S-y z'eSz'z Желательно обобщить этот частный случай, чтобы можно бы- ло в качестве фиксированной точки задавать любую точку, от- личную от точки 0 начала координат. При заданной фиксирован- ной точке F(xF, yF, zF) вместо 0, будем использовать разность х - jCpвместох их'-jCp — вместо*',, и так далее, что приведет к следующим формулам для вычисления масштабирования: х' - х¥ - 5Х • (х - *F) z'-zF = 5^ • (z-Zp) Эти формулы легко переписать в форме, более пригодной для эффективных вычислений х' - Sx • х + Сх y' = S -у + С2 где Ср С2,СЪ — константы, не зависящие от координат х, у, z, и, следовательно, вычисляемые только однажды перед началом процесса масштабирования: С1=Лр-5х • Хр C2-3fc-V>t В программе D3D операция масштабирования выполняется следующим образом. После команды Т при появлении вопроса "Масштаб! (Y/N)" нужно ответить нажатием клавиши У. Тог- да на экране появится следующий вопрос: Равномерный? (Y/N)
64 Глава 1. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ Если ответим У, то на экране появится строка Sx-Sy-Sz- и необходимо ввести требуемый коэффициент масштабирова- ния. Если ответом будет нажатие на клавишу N> то последова- тельно будут появляться три строки Sx- Sy- Sz- каждая из которых запрашивает ввод коэффициента масштаби- рования для указанного направления. Затем на экране появятся три строки Фиксированная точка: Центр (С), Начало (О) или Вершина (V): Теперь нужно нажать на клавишу, соответствующую одной из трех команд С, О или V. Из предыдущего объяснения должно быть ясно, что это означает указание точки начала координат О или вершины в качестве фиксированной точки для выполнения операции масштабирования. Если нажмем на клавишу v то программа запросит ввести номер точки: Номер точки: Третья опция — буква С, обозначающая центр, просто говорит о том, что в качестве фиксированной точки будет выбрана цент- ральная точка объекта. (Точка центра известна в любом случае, поскольку она используется в перспективном преобразовании для отображения объекта на экране). Если на экране видны оси, то их концевые точки также участвуют в вычислении положения центра всего объекта, так что вычисленное положение точки центра может несколько отличаться от ожидаемого. Если выбор фиксированной точки критичен, то лучше всего выбирать одну из опций О или V. При выборе опции V перед номером для фик- сированной точки можно указать знак минус, при "копирова- нии" это предотвратит присваивание точке нового номера. На рис. 1.22 показан пример равномерного масштабирования с коэффициентом 2. При выборе фиксированной точки значи-
1.8. ПРЕОБРАЗОВАНИЯ 65 тельно левее исходного объекта (изображенного слева), новый объект, увеличенный вдвое по сравнению с исходным объектом, изображается несколько правее. Рис. 1.22. Результат масштабирования 1.8.4. Зеркальное отражение Зеркальным отражением называется такое преобразование, которое формирует зеркальный образ объекта. Объект отражает- ся относительно плоскости, называемой плоскостью отраже- ния или просто зеркалом. На рис. 1.23 объект справа получен пу- тем зеркального отражения объекта, находящегося слева, отно- сительно вертикальной плоскости отражения, расположенной между двумя объектами. В программе D3D отражение выпол- няется очень просто. Если после команды Т ответом на запрос Отражение? (Y/N) будет нажатие на клавишу У, то на экране появятся две строки: PQR-плоск. отражения. Номера точек Р, Q, R: в которых говорится, что PQR является плоскостью отражения и после этого запроса необходимо указать номера точек Р, Q, R, которые не должны располагаться на одной прямой линии и ко- торые однозначно определяют плоскость отражения. И опять эти 3-273
66 Глава 1. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ Рис. 1.23. Результат отражения точки должны быть определены до обращения к команде Т. Они могут совпадать с некоторыми вершинами объекта или заданы совершенно отдельно. Подобно точкам Р и Q, используемыми в параграфе 1.8.1 для задания оси вращения, точки Р, Q, R в плос- кости отражения являются фиксированными точками. Их можно ввести с отрицательным знаком, если не требуется присваивание им новых номеров. Как и для других трех типов преобразований отображаемый объект может быть как "перенесен", так и "скопирован". На рис. 1.23 исходный объект присутствует в окончательном чертеже, то есть он был "скопирован", а не "перенесен" (отражение форми- рует зеркальный образ, форма которого обычно не идентична оригиналу, поэтому термины переместить и скопировать здесь не следует понимать слишком буквально!).
Глава 2 ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ 2.1. ОБЪЕКТЫ, СФОРМИРОВАННЫЕ ИЗ ПРЯМЫХ ПРИЗМ В программе D3D команды Read ("Чтение") и Transform ("Преобразование") /сокращенно обозначаемые через R и Г, по- зволяют строить достаточно сложные объекты сравнительно про- стым образом при условии, что эти объекты состоят из компонен- тов, для которых доступны объектные файлы. Благодаря воз- можностям масштабирования, можно даже пойти несколько дальше, и деформировать один исходный базовый элемент в це- лый ряд других с различными формами. Рассмотрим, например, куб с ребрами единичной длины, показанный на рис. 2.1. После вычерчивания этого куба способом, описанным в Главе 1, можно применить команду Write ("Запись") для получения файла CUBE.DAT со следующим содержимым: 1 0.000000 0.000000 0.000000 2 1.0000000.000000 0.000000 3 1.000000 1.000000 0.000000 4 0.000000 1.000000 0.000000 5 0.000000 0.000000 1.000000 6 1.0000000.000000 1.000000 7 1.000000 1.000000 1.000000 80.000000 1.000000 1.000000 Faces- Грани: 2376. 4158. 3487. 1265. 6785. 3214. з**
68 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ Рис. 2.1. Единичный куб Можно посчитать, что это не очень практичный объект, по- скольку кубы не часто встречаются в повседневной жизни. Но прямоугольные призмы различных видов встречаются весьма часто и с помощью программы D3D можно очень легко превра- тить куб в прямоугольную призму (имеющую только прямо- угольные грани), используя неравномерное масштабирование. Рассмотрим, например, стол, изображение которого показано на рис. 2.2. Этот стол состоит из девяти призм, их размеры в дюймах приведены ниже: Номер Количество Размер Размер Размер части по* по}' noz 1 1 35 50 2 2 4 3 3 29 3 2 1 40 3 4 2 25 2 3 Здесь вместо параметров длины, ширины и высоты мы пред- почли термины "размер по х", "размер по >" и "размер по z", поскольку это дает больше информации о том, как каждая часть будет использована для конструирования стола. В терминах
2.1. ОБЪЕКТЫ, СФОРМИРОВАННЫЕ ИЗ ПРЯМЫХ ПРИЗМ 69 (а) (б) Рис. 2.2. Стол
70 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ команд программы D3D эти три размера подсказывают, как при- менить неравномерное преобразование к единичному кубу на рис. 2.1. Например, часть 1, верхнюю столешницу стола, полу- чим при задании трех коэффициентов масштабирования Sx = 35, S = 50, 5Z = 2, когда, после команды Г, было выбрано неравно- мерное масштабирование. Как и обычно, существует несколько способов, приводящих к одинаковому результату, но для конст- руирования воображаемого стола рассмотрим здесь только один способ. Будем использовать точку начала координат 0 в качестве фиксированной точки для преобразования масштабирования, которое превратит единичный куб в нужную часть стола. После такого масштабирования для чарги 1, запишем полученные дан- ные в файл PART1.DAT, чтобы ее можно было загрузить впос- ледствии. Аналогичным образом получим файлы PART2.DAT, PART3.DAT и PART4.DAT для частей 2, 3 и 4. Теперь нам надо выбрать положение координатной системы относительно стола, но перед этим желательно уделить некото- рое внимание положению самого стола. Если бы нам потребова- лось конструировать реальный стол такого вида, то мы вероятнее всего поместили бы верхнюю крышку (столешницу) непосредст- венно на пол, затем сверху разместили ножки и так далее. В лю- бом техническом чертеже наиболее сложные детали должны быть изображены наиболее четко видимым образом и при этих условиях это означает, что рис. 2.2(a) совершенно непригоден для процесса конструирования. При вычерчивании перспективы точка наблюдения обычно располагается несколько выше на- блюдаемого объекта, это означает, что и рис. 2.2(6) не очень под- ходит для наших целей. К счастью, программа D3D позволяет повернуть стол, ножки которого направлены вверх, до придания ему нормального положения либо путем поворота (на 180° вок- руг горизонтальной оси), либо путем отражения (относительно горизонтальной плоскости отражения), так что почему бы не на- чать со стола, повернутого вверх ногами? Рассмотрим теперь стол, показанный на рис. 2.3(a). Основываясь на таком положении стола, изображение на рис. 2.3(6) можно назвать вид сверху. Технические чертежи, подоб- ные этой картинке, очень распространены, поскольку их гораздо легче начертить вручную, чем перспективное изображение, так как отрезки прямых линий, параллельные плоскости чертежа
2. L ОБЪЕКТЫ, СФОРМИРОВАННЫЕ ИЗ ПРЯМЫХ ПРИЗМ 71 (а) (0,0) (0,60) (95,0) (35.50) (б) Рис. 2.3. Стол в процессе конструирования
72 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ Рис. 2.4. Координатная система изображаются в натуральную величину. Обратите внимание, что на рис. 2.3(6) также показаны и номера частей, которые ис- пользованы для формирования этдго чертежа. Теперь, когда стол располагается в удобном положении для нашей работы, можно определить координатную систему, в ко- торой предстоит работать. Будем считать, что плоскость, в кото- рой ножки стола соприкасаются со столешницей, совпадает с плоскостью ху, что показано на незавершенном столе, изобра- женном на рис. 2.4. Позиции наиболее важных точек (с нулевой координатой z), отмеченные на рис. 2.3(6), основаны именно на этой координатной системе. В действительности всегда жела- тельно выполнить эскиз этого вида сверху, чтобы избежать оши- бок во время фактического процесса конструирования. После всех этих подготовительных действий, объединим раз- личные части, записанные в файлах с PART1.DAT до PART4.DAT, чтобы сконструировать стол. Последовательно за- грузим часть 1, часть 2, часть 3 и часть 4 (используя команду R) и помощью команды Т перенесем каждую из них в ее точную позицию. Каждый раз операция переноса применяется к опре- деленному диапазону точек, которые либо читаются из файла, либо определяются в результате преобразования.
2.2. НЕКОТОРЫЕ ДРУГИЕ СТАНДАРТНЫЕ КОМПОНЕНТЫ 73 Начнем с загрузки файла PART1.DAT (командой R) и вы- полним перенос на delta x = delta .у = О, delta z = -2. После этого загрузим файл PART2.DAT, причем после ввода команды R обя- зательно нужно нажать на клавишу N в ответ на вопрос Очистить экран? (Y/N) так что обе части, часть 1 и часть 2, появятся на экране одновре- менно. Вершины вновь загруженной части 2 будут автоматиче- ски перенумерованы и диапазон новых номеров (9—16) отобра- зится в рабочей части экрана. Это позволяет переместить часть 2 в ее точную позицию (оставляя положение части 1 неизменным) посредством переноса на delta х = 2, delta у = 2, delta z = 0. Для второй ножки можно использовать команду С — "Копия" и за- дать для переноса параметры delta х = 0, delta у=43, delta z = 0 для точек, описывающих первую ножку в диапазоне (9—16) и так далее. Когда формирование стола в перевернутом положении бу- дет закончено, его можно зеркально отразить относительно лю- бой горизонтальной плоскости, скажем, плоскости, проходящей через точки 1, 2 и 3, для переворота в нормальное положение. Затем по команде Я можно получить изображение стола как сплошного тела. Конечно, этому должна предшествовать коман- да К для выбора подходящей точки наблюдения. Рис. 2.2(a) и (б) были получены именно таким образом со следующими значени- ями сферических координат положения глаза: (а)Л = ЗОО,0 = 2О°,р = 65°, (б)Д=15О,0 = 2О°,у> = 95° На этом конструирование стола заканчивается. Это всего лишь пример объекта, составленного только из прямых призм. Существует множество других применений, в которых объекты состоят исключительно или в основном из прямых призм, поэто- му программа D3D может оказаться полезной не только для кон- структоров столов, но и для многих других. 2.2. НЕКОТОРЫЕ ДРУГИЕ СТАНДАРТНЫЕ КОМПОНЕНТЫ Мы могли убедиться, что куб является очень полезным ком- понентом, поскольку из него можно получить любую прямую призму (но только с прямоугольными гранями). В ряде примене- ний нам могут потребоваться некоторые другие стандартные
74 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ компоненты кроме кубов. Если только они не будут слишком сложными, их можно построить с помощью программы D3D, но это не всегда наиболее эффективный способ, особенно в тех слу- чаях, когда требуется определить большое число граней, которы- ми аппроксимируются кривые поверхности. Примером такого объекта может служить прямой цилиндр. Можно рассмотреть идею использовать всегда цилиндр с единич- ным диаметром и единичной высотой, аналогично тому, как было принято для куба, но здесь мы встречаемся с проблемой, которая не возникала в связи с кубом. Напомним, что в качестве ограничивающих граней можно использовать только полигоны, поэтому нам необходимо аппроксимировать круговое основание цилиндра с помощью правильного многоугольника, имеющего, например, п вершин. Возникает вопрос, какое значение п следу- ет выбрать. Чем больше значение п> тем лучше аппроксимация, но объект будет получен более дорогим путем в терминах затрат времени на вычисления и занимаемого объема памяти. Было бы хорошо, если бы можно было выбрать любое значение п. Поскольку программа D3D уже довольно сложная, то в нее не включена возможность генерации любой степени аппроксима- ции для цилиндра (и других подобных сплошных тел). Для этой цели будем применять отдельные специальные программы. Как можно видеть из справочника программного диска, на нем есть несколько программ, кроме D3D. Одна из них имеет имя CYLINDER. Если в вашем распоряжении еще нет этого програм- много диска, то исходный текст этой программы приведен в пара- графе 3.2 данной книги. Программа запускается на выполнение вводом ее имени CYLINDER После этого последовательно на экране появляются вопросы: Направление оси? (X/Y/Z) Диаметр? Высота? Число вершин полигона? Имя выходного файла? Ось цилиндра должна быть параллельна одной из трех коор- динатных осей и первые три вопроса позволяют немедленно оп- ределить цилиндр с точной ориентацией и точными размерами,
2.2. НЕКОТОРЫЕ ДРУГИЕ СТАНДАРТНЫЕ КОМПОНЕНТЫ 75 что исключает необходимость применения операций поворота и масштабирования для достижения нужного результата. Нор- мально может потребоваться операция переноса, поскольку центр цилиндра будет совпадать с точкой начала системы коор- динат, что в большинстве случаев не то, что нам нужно. Ответ на четвертый вопрос определит действительную форму объекта. Пусть это количество ребер полигона будет обозначено через л. Фактически рассматриваемое сплошное тело представляет собой призму, а не цилиндр. Если задать п в 4, то получим прямую призму, основанием которой является квадрат. Обычно выбира- ется несколько большее значение л, скажем 30. Результирую- щий аппроксимированный цилиндр будет записан в файл, имя которого вводится в качестве ответа на пятый вопрос. Этот файл будет иметь формат, необходимый для программы D3D и обсуж- давшийся в параграфе 1.6. Таким образом, после выполнения программы CYLINDER можно будет запустить программу D3D с только что сформированным файлом в качестве входного файла, как показано на блок-схеме рис. 2.5. Программа CYLINDER / Объектный файл / Программа D3D Перспективное изображение Рис. 2.5. Объектный файл, сформированный программой CYLINDER и используемый программой D3D
76 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ Если программа CYLINDER выполнена со следующими отве- тами на пять вопросов: Z 5 2 30 CYL.DAT то можно будет запустить на выполнение программу D3D, напе- чатав D3D CYLDAT затем по команде Н (с принимаемым по умолчанию пороговым значением 35°) будет получено изображение рис. 2.6. Хотя на этой картинке и не показано, начало системы координат лежит точно в центре цилиндра. Рис. 2.6. Цилиндр Конус может быть аппроксимирован пирамидой, что анало- гично аппроксимации цилиндра призмой. Для этого воспользу- емся программой CONE, описанной в параграфе 3.3. Программа генерирует прямой конус с основанием в виде окружности, рас- положенной на плоскости ху и с вершиной на положительной по- . луоси z. Программа запускается вводом команды CONE Затем нужно ответить на вопросы, появившиеся на экране (учи- тывая текст включительно до вопросительного знака): Диаметр окружности основания? 5 Высота? 3 Число вершин полигона? 30 Имя выходного файла? CONE.DAT
2.2. НЕКОТОРЫЕ ДРУГИЕ СТАНДАРТНЫЕ КОМПОНЕНТЫ 77 Если на эти вопросы будут введены ответы, показанные после вопросительного знака, то при запуске программы D3D с файлом CONE.DAT в качестве входного файла и ввода команды Н с по- роговым значением 0 будет сформировано изображение конуса, показанное на рис. 2.7. Заметим, что наш конус считается сплошным телом. На рис. 2.7 точка наблюдения находится не- сколько ниже объекта, поэтому мы рассматриваем его как бы снизу. Поскольку основание непрозрачно (правильный много- угольник с 30 ребрами), то можно видеть только те ребра наклон- ных треугольников, которые расположены на переднем плане, а находящиеся на заднем плане оказываются невидимыми. Рис. 2.7. Конус Другим стандартным элементом является сфера. Существует несколько способов аппроксимации сферы с помогцью объектов, ограниченных плоскими гранями, и здесь воспользуемся двумя совершенно различными способами. Первый способ будет осно- ван на окружностях, которые обычно определяются на глобусе, как показано на рис. 2.8. Имеем два полюса — северный полюс расположен вверху, южный полюс — внизу и п вертикальных окружностей — линий меридианов, которые проходят через по- люса. Все вертикальные окружности имеют одинаковый радиус г, который совпадает с радиусом сферы. Сфера разделяется на т горизонтальных слоев, которые делят каждую полуокружность между северным и южным полюсами на т дуг равной длины. Тем самым определяется т - 1 горизонтальных окружностей или линий параллелей. Оба полюса и все точки пересечений линий меридианов и параллелей являются вершинами полигонов, их мы и будем использовать в качестве граней аппроксимирующего многогранника. Эти полигоны являются трапециями, за исклю-
78 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ Рис. 2.8. Аппроксимация сферы при т-10, п-20 чением находящихся вблизи полюсов. Совершенно ясно, что чем больше будут заданы значения для лит, тем лучше будет апп- роксимация. Однако, при этом возрастет и вычислительное вре- мя, особенно если понадобится удаление невидимых линий. Тра- пеции на сфере вблизи "экватора" (то есть на самом большом слое) будут почти квадратными, если выбрать п = 2т поскольку имеем п точек на каждой горизонтальной окружности и 2т точек на каждой вертикальной окружности. Программа SPHERE, приведенная в параграфе 3.4, генерирует такую ап- проксимированную сферу. Пользователь должен ввести значе- ния /гит, радиус сферы и имя выходного файла. Центр сферы располагается в начале системы координат, а полюса находятся на оси z. Сфера, показанная на рис. 2.8 получена с помощью программ SPHERE и D3D при значениях m - 10 и п = 20. Хотя эта картинка и выглядит привлекательной, она несколь- ко неудовлетворительна тем, что элементарные плоские грани имеют неодинаковые размеры. Кроме всего прочего, в целях по- вышения эффективности желательно иметь как можно меньше элементарных площадок, при условии, что полученный объект
2.2. НЕКОТОРЫЕ ДРУГИЕ СТАНДАРТНЫЕ КОМПОНЕНТЫ 79 Рис. 2.9. Сфера, аппроксимированная с помощью 80 треугольников не будет очень сильно отличаться от реальной сферы и поэтому очень неэффективно решение, при котором около полюсов со- средоточено довольно много маленьких граней. Другой вполне возможный нежелательный эффект заключается в том, что при изображении объекта с видимыми линиями параллелей и мери- дианов, положение точки наблюдения очень сильно влияет на характер изображения. Поэтому рассмотрим альтернативный способ аппроксимации сферы, а именно с помощью правильного многогранника с 80 гранями в виде треугольников. В параграфе 3.6 этот объект будет исследован более подробно, а здесь будем просто его использовать. Нам всего-навсего нужно прочитать файл SPH80.DAT, (сгенерированный программой SPH80, опи- санной в параграфе 3.6). Обсуждаемый многогранник показан на рис. 2.9. Изображение может быть получено вводом команды D3D SPH80.DAT и последующим заданием команд Я и Р. На рис. 2.10(a) и (б) показана комбинация всех стандартных элементов, описанных к данному моменту. Внизу находится квадратная плита, в центре которой располагается конус. К вер- шине этого конуса прикреплена нижняя из семи сфер, соединен- ных между собой шестью тонкими цилиндрами. Семь сфер пред- ставлены в виде многогранников из 80 треугольных граней. Изо-
80 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ бражения сфер получаются путем только однократного считыва- ния файла SPH80.DAT для сферы в центре. Эта сфера затем "ко- пируется" путем переноса для формирования сферы на переднем плане (с центром, лежащим на положительной полуоси х). Данные для цилиндра считываются из файла, полученного в результате работы программы CYLINDER, также однократно и этот цилиндр затем "переносится1' в его точную позицию между двумя первыми сферами. Для выполнения операции поворота вокруг осей системы координат были предварительно определе- ны точки (1,0,0), (0,1, 0) и (0,0,1), которым приписаны номера 1, 2, 3 соответственно (напомним, что номером 0 обозначается точка начала системы координат). Затем сфера и цилиндр на пе- реднем плане повертываются вокруг оси у на 180° для получения сферы и цилиндра на заднем плане. Поскольку последние два цилиндра и две сферы имеют непрерывную последовательность образующих их точек, то все четыре элементарных объекта мож- но одной операцией повернуть на 90° вокруг оси z для получения сфер и цилиндров, лежащих на положительной и отрицательной полуосях у. И, наконец, поворот тех же двух сфер и двух цилин- дров на угол 90° вокруг оси х дает в результате сферы и цилиндры на оси z. Сложные объекты, подобные только что описанному, целе- сообразно разделять на две или более частей и записывать их в отдельные файлы. Для данного примера было задействовано два файла: один — для семи сфер с соединяющими их цилиндрами, а другой — для квадратной плиты с конусом на ней. Только после завершения формирования каждой из этих двух частей, они бы- ли объединены, причем сначала считывается первая (то есть бо- лее сложная) часть, а затем — вторая (плита с конусом), которая переносится вниз заданием отрицательного значения для delta z. На рис. 2.10(6) показан тот же объект, что и на рис. 2.10(a), но рассматриваемый с другой точки наблюдения и с "пороговым" значением 35°, чтобы исключить линии пересечения, которые реально не существуют на соответствующих кривых поверхно- стях. Это дело вкуса оставлять ли такие линии вычерченными, или удалять их. Поскольку на рис. 2.10(a) изображается гораздо больше отрезков прямых линий, чем на рис. 2.10(6), то затраты времени на удаление невидимых линий для первого рисунка ока- зываются значительно больше, чем для второго.
2.3. НЕКОТОРЫЕ ДРУГИЕ СТАНДАРТНЫЕ КОМПОНЕНТЫ 81 Рис. 2.10. Объект, составленный из стандартных элементов
82 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ 2.3. ТЕЛА ВРАЩЕНИЯ И РАЗРЕЗЫ В предыдущем параграфе уже применялись некоторые про- граммы для специальных тел вращения, а именно цилиндра, ко- нуса и сферы. Эти программы, написанные на языке программи- рования Си, приведены в главе 3. Если читатель лишь немного знает о языке Си, интересуется им, но хочет узнать побольше и желает воспользоваться советом автора, то ему можно пореко- мендовать книгу автора "Язык Си для программистов". Если же изучение языка Си не предполагается, то можно выбрать одну из двух возможностей. Во-первых, если читатель уже умеет про- граммировать на каком-либо языке, тогда его можно использо- вать для генерации файлов, пригодных в качестве входных фай- лов для программы D3D. Как очевидно, только содержимое этих файлов имеет значение и программа D3D никак не может рас- познать, каким образом эти файлы были подготовлены. Так что, если, для примера, читатель предпочитает вместо языка Си при- менять языки Бейсик или Паскаль, то он вполне может восполь- зоваться этими языками. Во-вторых, читатель вообще совсем не может или не хочет программировать. Но и в этом случае можно применять программу D3D для создания новых интересных объ- ектов, даже если они не могут быть составлены из доступных стандартных элементов. Пример такого объекта показан на рис. 2.11. Это неполное тело вращения называется вид с разрезом. Он Рис 2.11. Вид с разрезом
2.3. ТЕЛА ВРАЩЕНИЯ И РАЗРЕЗЫ 83 Рис. 2.12. Разрез по оси вращения получается при повороте контура, показанного на рис. 2.12 вок- руг его оси на угол 270°. Этот неполный объект может быть использован для получения четкого представления о внутренней структуре соответствующего полного объекта, который был бы получен при повороте исходного контура на 360° вместо 270°. Рис. 2.11 можно получить, используя только программу D3D. Начнем с определения точек с 1 по 9, показанных на рис. 2.12 (задавая положение точки наблюдения как р = 1 000 000, в = 90°, (р = 90°). Координаты этих точек могут быть следующими: х у z 1 2 3 4 5 6 7 8 9 00 10 0 10 0 10 0 60 20 20 0 0 00 0 0 2 4 4 6 8 8 2 Точка 1 совпадает с началом системы координат, а точка 2 ле- жит на положительной полуоси х (направленной влево на рис. 2.12); все координаты у равны нулю, следовательно работа осу- ществляется в плоскости xz. Пока линии нам не нужны, поэтому в действительности начнем с версии рис. 2.12 без каких-либо линий. Используя команду Т будем поворачивать все точки, за исключением 1 и 2, вокруг оси х на угол 15°. В ответ на запрос будем указывать числа 3 и 9 в качестве верхней и нижней границ
84 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ диапазона номеров вершин, а после выбора операции поворота будем вводить номера 1 и 2 для точек Р и Q, необходимых для описания оси вращения. В этой и всех последующих операциях поворота используем команду Копия, а не Перенос, поэтому ее не нужно указывать каждый раз, не требуется также повторять и ввод номеров 1 и 2 для определения оси вращения. Номер каждой новой точки получается путем увеличения на 7 номера соответ- ствующей старой точки, что дает номера точек с 10 по 16. Это позволяет определить семь граней следующим образом: F 3 4 5 6 7 8 9 -4 11 5 12 6 13 -7 14 8 15 -9 16 3 10 -10. 11. 12. -13. 14. -15. 16. Знак минус предотвращает от вычерчивания некоторых от- резков прямых линий. Например, отрезок прямой линии (11,10) не должен быть виден как ребро в конечном результате, показан- ном на рис. 2.11. Хотя отрезок (3,4) в некоторых ситуациях и должен быть вычерчен, здесь он интерпретируется таким же об- разом, как отрезок (11, 10), имея в виду последующие операции копирования. С исключением некоторых опущенных отрезков прямых линий получим теперь то, что будем называть сектором 15°. Этот сектор должен превратиться в сектор 270°; хотя нам нужно было бы поворачивать и копировать сектор 15° семнад- цать раз (что привело бы к формированию сектора 18 * 15° = 270°), требуемый результат можно получить за пять поворотов, как показано в таблице 2.1 Таблица 2.1 Повороты для превращения сектора 15° в сектор 270° Нижняя граница 3 3 31 3 87 Верхняя граница 16 30 58 86 170 Угол поворота 15° 30° 30° 90° 90° Диапазон новых точек 17— 30 31— 58 59— 86 87 — 170 171-254 Результирующий сектор 30° 60° 90° 180° 270°
2.4. ГЛАДКИЕ ПРОСТРАНСТВЕННЫЕ КРИВЫЕ 85 На каждом шаге поворачивается (и копируется) сектор с уг- лом, равным углу поворота, указанным в третьем столбце. После этих поворотов необходимо добавить горизонтальную и верти- кальную граничные грани, плоскости которых проходят через ось вращения: F 9 8 7 6 5 4 3. 248 249 250 251 252 253 254. Напомним, что все вершины должны обходиться в направле- нии против часовой стрелки при рассматривании их извне объек- та — следовательно, должен соблюдаться порядок уменьшения номеров для первой грани, и порядок увеличения для второй в этих двух последовательностях номеров вершин. Только после команды Я, когда невидимые линии должны быть удалены, мы получим приятное для глаза изображение, что и показано на рис. 2.11. Здесь были использованы сферические координаты точки наблюдения/) = 50, в = 65°, <р = 70°. В процессе конструирования объект изображается в виде проволочной моде- ли без некоторых граничных граней. Поскольку в этом объекте содержится очень много точек, то отображение номеров точек только мешает, также не нужны оси и утолщенные линии, поэ- тому перед выполнением операций преобразований рекоменду- ется выполнить команду М. 2.4. ГЛАДКИЕ ПРОСТРАНСТВЕННЫЕ КРИВЫЕ Существует математический способ, известный под именем аппроксимации с помощью В-сплайнов, который может быть ис- пользован для получения гладких кривых очень удобным обра- зом. Будем использовать этот способ для описания кривых в трехмерном пространстве, подобно показанной на рис. 2.15. Можно начать с ограниченного числа точек, определенных с по- мощью программы D3D. По команде W координаты этих точек могут быть записаны в файл и этот файл затем считывается но- вой программой, CURVE3, приведенной в параграфе 3.8. Эта программа генерирует большое количество других точек путем интерполяции с помощью В-сплайна; здесь этот способ обсуж- дается исключительно с позиций пользователя.
86 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ Программа CURVE3 запрашивает три вида информации, вводимой через клавиатуру: 1. Имя входного файла; 2. Имя выходного файла; 3. Целое число N> обсуждаемое ниже. Оба файла, входной и выходной, имеют структуру, совмести- мую с требованиями программы D3D, то есть они имеют формат объектных файлов, считываемых и записываемых программой D3D. Целое число N определяет количество интервалов между двумя последовательными заданными точками: при т заданных точках выходной файл будет содержать к точек, где k=(m-3)N+l как мы увидим в конце этого параграфа. Благодаря совместимости с программой D3D файлов, считы- ваемых и записываемых программой CURVE3, программу D3D можно использовать как для подготовки входных файлов для программы CURVE3, так и для отображения сгенерированной кривой, записанной в выходном файле. D3D -* файл - CURVE3 -» фдйл - D3D Программа CURVE3 использует только первую часть входно- го файла, содержащую номера и координаты всех точек. Нали- чие второй части, начинающейся со слова Faces ("Грани"), про- сто игнорируется. Фактически программа CURVE3 также игно- рирует и номера точек, которые означают, например, что файл 9 1.53.5 2.5 5 1.03.0 3.0 7 0.50.7 2.5 8 0.10.2 0.0 эквивалентен файлу 1 1.53.5 2.5 2 1.03.0 3.0 3 0.50.7 2.5 4 0.10.2 0.0 если его рассматривать с точки зрения программы CURVE3. К счастью, программа D3D записывает точки в порядке увеличе- ния их номеров, поэтому она никогда не создаст файл, который мог бы привести к возможной путанице. Об этом здесь упоми-
2.4. ГЛАДКИЕ ПРОСТРАНСТВЕННЫЕ КРИВЫЕ 87 нается лишь постольку, поскольку такой файл может быть со- здан каким-либо иным способом, например, с помощью тексто- вого редактора или какими-либо собственными программами, и тогда необходимо знать, что линии в файле должны быть описа- ны в правильной последовательности. Заметим, что в этом заключается отличие программы D3D, для которой номера точек во входном файле играют существенную роль. Рисунки 2.13, 2,14 и 2.15 были получены именно в указанном порядке. На рис. 2.13 отмечены точки с 1 по 15, лежащие на не- которой спирали вместе с отрезками прямых линий, соединяю- щими эти точки. Напомним, что такие отрезки прямых линий могут быть заданы в объектном файле в виде номеров пар точек после ключевого слова Faces С*Грани"), или, при использовании программы D3D, их номера вводятся после команды F> как в сле- дующем примере: F 1 2. 2 3. 3 4. Как мы только что видели, этот список номеров можно опу- стить, если предполагается использовать только программу CURVE3, но они были введены с целью получения более ясной Рис. 2.13. Некоторые точки на спирали
88 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ картинки рис. 2.13. Затем было применено преобразование (в ре- жиме "Перенос"), а именно — поворот последовательности то- чек с нижней границей 9 и верхней границей 15 на угол 90° вок- руг прямой линии, проходящей через точки 7 и 8. Результирую- щий объект, показанный на рис. 2.14, был записан в файл, на- пример, TEST.DAT. Затем этот файл был обработан программой CURVE3. После запуска программы на экране появляются три вопроса, которые вместе с ответами на них, показаны ниже: Входной файл: TEST.DAT Выходной файл: TEST1.DAT Введите число N - количеству интервалов между двумя последовательными точками: 4 Наконец, необходимо ввести команду: D3DTEST1.DAT и использовать команды Ей Р для получения рис. 2.15. Вместо 15 точек из файла TEST.DAT, сформировано 49 точек в файле TEST1.DAT. Они были получены с помощью В-сплайнов и, хотя пользователь не должен беспокоиться о математических тонко- стях этого способа, нужно помнить о двух важных моментах: они могут не понравиться, но они являются платой за очень хорошую гладкость полученной кривой. Рис. 2.14. Результат после поворота точек с 9 по 15
2.4. ГЛАДКИЕ ПРОСТРАНСТВЕННЫЕ КРИВЫЕ 89 1. В общем случае кривая не проходит точно через заданные точки, она только использует их для формирования гладкой кривой. Чем ближе друг к другу расположены заданные точ- ки, тем лучше будет аппроксимация, поэтому если получен- ная кривая В-сплайна не очень хорошая, ее можно улуч- шить заданием большего количества точек. 2. Самая первая и самая последняя точки не включаются в со- став кривой. В нашем примере конец кривой внизу рис. 2.15 близко подходит к точке 2 (см. рис. 2.13), а на другом конце, вверху рисунка, кривая заканчивается вблизи предпослед- ней точки (точка 14) ломаной линии рис. 2.14. Отсюда сле- дует, что для В-сплайна необходимо задавать, по крайней мере четыре точки А, В, С, D. В этом случае результирую- щая кривая будет располагаться примерно между точками В и С. Хотя точки А и D и не охватываются, они влияют на формируемую кривую, так что их нельзя опускать. Даже без использования сложных математических приемов теперь можно понять формулу *=(m-3)7V+l упомянутую ранее. Имеются т заданных точек, из которых пер- вая и последняя не аппроксимируются, поэтому только т - 2 из Рис. 2.15. Результат аппроксимации с помощью В-сплайна
90 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ них будут соединены сглаживающей кривой. Вначале предполо- жим, что они соединяются отрезками прямой линии; поскольку количество соединяющих отрезков всегда на единицу меньше количества соединяемых точек, то получим только т - 3 таких отрезков прямой линии. Каждый из этих отрезков делится на N интервалов, что вместе дает (т - 3)N маленьких отрезков пря- мой линии. Они соединяют к новых точек и, опять применяя пра- вило, что количество соединяющих отрезков на единицу меньше числа соединяемых точек, получим вышеприведенное выраже- ние для к. Заметим, что то, что мы здесь называем "кривая", в действи- тельности является последовательностью из (т - 3)N отрезков прямой линии. При выборе достаточно большого значения N эти отрезки прямой линии будут очень маленькими, поэтому можно получить очень хорошую аппроксимацию реальных кривых, что и демонстрирует рис. 2.15. 2.5. КАБЕЛИ И УЗЛЫ Трехмерные кривые в параграфе 2.4 являются математиче- ской абстракцией: их можно рассматривать как нити с нулевым диаметром. Теперь желательно их преобразовать в реальные сплошные тела: гибкие стержни или кабели с диаметром больше 0. Таким же образом, как мы можем представить получение ци- линдра из отрезка прямой линии (ось цилиндра), задав для него некоторую толщину, так и кабель можно получить из простран- ственной кривой. Аналогия между цилиндром и кабелем подска- зывает способ аппроксимации последнего: как обычно, кривая поверхность кабеля будет заменена большим количеством плос- ких ограничивающих граней. Как это делалось и в предыдущем параграфе, будем использо- вать программу, которая преобразует один файл, совместимый с программой D3D в другой файл. Такая программа с именем CABLE приведена в параграфе 3.9. Входной файл для нее лучше создавать с помощью программы CURVE3 (см. параграф 2.4), а ее выходной файл будет нормально считываться программой D3D. Поэтому будем использовать последовательно программы D3D, CURVE3, CABLE, D3D именно в этом порядке. (Конечно, всегда рекомендуется отобразить кривую на экране дисплея, прежде чем применять для нее программу CABLE, что означает
2.5. КАБЕЛИ И УЗЛЫ 91 необходимость использования программы D3D также между программами CURVE и CABLE исключительно для проверки полученной кривой на соответствие ожидаемому результату). Подобно программе CURVE3, программа CABLE задает не- сколько вопросов, а именно запрашивает имена входного и вы- ходного файлов, количество точек, которые должны быть опре- делены на каждой окружности и диаметр этих окружностей. Эти окружности аналогичны основанию цилиндра или конуса: если задать п точек на каждой окружности, тогда каждое сечение ка- беля будет правильным многоугольником с п сторонами. Если п будет выбрано очень большим, то получим очень хорошую апп- роксимацию кабеля, но не следует забывать, что может потребо- ваться удаление невидимых линий, а этот процесс потребует довольно значительных затрат времени, если будет очень много ограничивающих граней. Как мы видели в конце предыдущего параграфа, программа CURVE формирует (т- 3)N небольших отрезков прямых линий, каждый из которых теперь будет раз- вернут в п граней, так что число граней, аппроксимирующих кривую поверхность кабеля равно (m-3)Nn где т = количество точек, которые заданы для аппроксимации с по- мощью В-сплайна, то есть число точек в файле, считывае- мом программой CURVE3; N= количество интервалов между двумя последовательными точками (число N вводится при работе программы CURVE3); п- количество точек на каждом поперечном сечении кабеля (число п вводится при работе программы CABLE). Очень полезно всегда начинать с относительно малого значе- ния п и увеличивать его позднее, если результат окажется неу- довлетворительным. При п = 8 был получен файл, который после считывания его программой D3D, послужил основой для рис. 2.16. Поэтому здесь число граней на кривой поверхности кабеля было <15-3)х4х8 = 384
92 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ Рис. 2.16. Кабель Хотя различие между толстыми и тонкими линиями позволя- ет в некотором роде представить себе "глубину" кривой на рис. 2.15, кабель на рис. 2.16 показывает пространственную ситуа- цию более ясно. Программы CURVE3 и CABLE позволяют сформировать трехмерные картинки узлов. Рассмотрим для примера рис. 2.17 в связи со следующим ассоциированным файлом данных, который должен быть использован в качестве входных данных для про- граммы CURVE3: 1 2 3 ■ 4 5 6. 7 8 9 10 11 12 13 14 15 0 0 -15 15 0 -15 0 15 -15 0 0 0 15 -15 0 100 2 -70 -30 0 15 20 22 -30 20-22 0-15 -30 -70 0-100 70-50 30 -8 0-15 -20
2.5. КАБЕЛИ И УЗЛЫ 93 16 15 17 0 18-15 19 15 20 0 21 0 Faces■ 1 2. 2 3. 3 4. 4 5. 5 6. 6 7. 7 8. 8 9. 9 10. 10 11. 11 12. 12 13. 13 14. 14 15. 15 16. 16 17. 17 18. 18 19. 19 20. 20 21. 30 70 100 -30 0 -20 22 0 15 8 10 12 - Грани: Этот файл может быть получен либо с помощью программы D3D, либо обычного текстового редактора, но можно и смешать Рис. 2.17 Входные данные для программы CURVE3, отображенные с помощью программы D3D
94 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ оба эти способа: сначала применить программу D3D для получе- ния нечто похожего на рис. 2.17 (при 0 = 0° и <р = 90°), при этом все точки пока будут расположены в плоскости yz, а затем можно воспользоваться текстовым редактором для изменения нулевой jc-координаты для всех 21 точек до их нужных значений. Напри- мер, две точки 4 и 18 первоначально имеют одинаковые коорди- наты, а именно х = 0, у = 0, z = 15. Поскольку точка 4 должна лежать на заднем плане, зададим для нее jc-координату -15, тог- да как точка 18, находящаяся на переднем плане должна полу- чить координату ху равную 15. (Напомним, что система коорди- нат правая, поэтому положительная полуось х на рис. 2.17 на- правлена в сторону зрителя). Как только будет выполнена эта несколько утомительная работа (но уж не слишком чрезвычай- но!) по созданию этого файла, компьютер сделает всю действи- тельно тяжелую работу по формированию различных изображе- ний реалистичного рифового узла. После применения програм- мы CURVE3 к этому файлу, например, при значении N = 5, получим рис. 2.18 вместе с ассоциированным объектным файлом. Затем к этому последнему файлу можно применить программу CABLE, что дает окончательный результат, показанный на рис. 2.19(a), (б), (в). Здесь для каждой окружности в качестве вели- чины радиуса было задано значение г = 2 и число точек на ней п = 10. Для всех трех задействованных файлов соответствующие картинки были получены с помощью программы D3D. Причем эта последняя программа фактически выполняет основную часть Рис. 2.18. Результат работы программы CURVE3
2.5. КАБЕЛИ И УЗЛЫ 95 (а) (в) Рис. 2.19. Рифовый узел
96 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ работы: сравнительно трудоемкий в смысле затрат вычислитель- ноге* времени процесс удаления невидимых линий (по команде Н). В подобных случаях реально затраченное время зависит от скорости работы применяемого компьютера и заданых величин Nun. Эта работа выполнялась автором всего лишь на процессоре 8088, но скорость может быть значительно повышена, если рабо- ту выполнять на персональном компьютере типа AT, оснащен- ном математическим сопроцессором. Хотя и менее драматично, но на вычислительное время сильно влияет разумный выбор до- статочно большого значения "порогового угла" а, что приводит к исключению из результата некоторых искусственных ребер на кривой поверхности. Здесь были использованы значения а = 35° для рис. 2.19(a) и (б), и а = 50° для рис. 2.19(b). При а = 35° вы- черчиваются ребра, параллельные оси кабеля, так как для п = 10 360°/л = 36° > 35° Ребра в точках стыка на рис. 2.19(a) и (б) как следствие срав- нительно большого угла а = 35° оказались исключенными, но они были бы вычерчены (как это было сделано на рис. 2.16), если бы угол а был равен 0 или, что эквивалентно, был дан отрицатель- ный ответ на вопрос о кривых поверхностях после команды Я. На рис. 2.20 показан другой результат (очень похожий на тот, что был нарисован М. Эшером вручную!).. Он был получен совер- шенно аналогичным способом, который только что обсуждался. Сначала отработала программа CURVE3 с нижеследующими входными данными, затем были выполнены программы CABLE и D3D. 1 0 1.73 -1 2-2 1.73 1 3 2 0 2 4 0-1.73 1 5-2 0 0 6 2 1.73 1 7 0 1.73 3 8-2 0 2 9 2 0 0 10 0 1.73 -1 11-2 1.73 1 12 2 0 2 В противоположность рис. 2.19 было использовано значение а = 0°, так что на рис. 2.20 изображены все ребра. Поскольку про-
2.6. ВИНТ С ПРЯМОУГОЛЬНОЙ РЕЗЬБОЙ 97 Рис. 2.20. Узел поЭшеру граммы CURVE3 и CABLE предназначены для обработки конеч- ных кривых и кабелей, совершенно не очевидно, что бесконеч- ный "кабель" рис. 2.20 может быть сделан таким же образом. Как будет показано в параграфе 3.8 в этом отношении с програм- мой CURVE3 нет никаких проблем. Однако, задавая кривой не- которую толщину (в программе CABLE), вместо окружности ис- пользуется некоторый правильный многоугольник и совершенно не обязательно, чтобы вершины полигона точно совпали с таки- ми же вершинами начального полигона. Поэтому следует при- знать, что два конца кабеля, показанного на рис. 2.20 точно не стыкуются, но для получения этого рисунка была выбрана такая точка наблюдения, что неточная стыковка оказалась скрытой. 2.6. ВИНТ С ПРЯМОУГОЛЬНОЙ РЕЗЬБОЙ В этом параграфе рассмотрим программу, которая может ока- заться полезной в инженерной механике. Здесь пойдет речь о винтовой нарезке с прямоугольным профилем, как показано на рис. 2.21 (а) и (б). Программа с именем SCRTHR генерирует объ- ектный файл, который может быть прочитан программой D3D для формирования проекции трехмерного объекта. Текст про- граммы SCRTHR приведен в параграфе 3.12. Винтовая нарезка — не очень простой геометрический объект и может оказаться, что программу SCRTHR не очень просто понять. Тем не менее, применять эту программу легко. Она запрашивает несколько 4 — 273
98 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ существенных размеров: большой диаметр D, малый диаметр d> шаг резьбы Р и желаемую длину LQy показанные на рис. 2.21 (а). Фактически, если винтовая нарезка действительно квадратная, то сторона каждого квадрата имеет длину O.S(D-d) что равно половине шага резьбы, то есть имеем P~D-d Поэтому, если винтовая нарезка действительно квадратная, то величину шага можно вычислить по значениям большого и малого диаметров и его нет необходимости задавать отдельно. Но программа SCRTHR несколько более универсальна в том отно- шении, что она позволяет задавать шаг независимо от значений двух диаметров. И таким образом можно задавать винт не только с квадратным профилем резьбы, но и с любым прямоугольным. Как обычно, кривая поверхность аппроксимируется плос- кими полигонами. Также необходимо задать величину к — число шагов на половинуоборота. Напомним, что в параграфе 2.2 осно- (а) Рис. 2.21. Винт с прямоугольной резьбой
2.6. ВИНТС ПРЯМОУГОЛЬНОЙ РЕЗЬБОЙ 99 (б) (в) Рис. 2.21. Винт с прямоугольной резьбой
100 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ вание цилиндра аппроксимировалось правильным многоуголь- ником, образованным 30 ребрами. Для получения аналогичной аппроксимации здесь потребуется задать параметр к равным 15, так как при этом на полный оборот потребуется 30 шагов, как и в случае цилиндра. Поскольку винтовая поверхность много слож- нее цилиндрической, предпочтительно начать с меньшего значе- ния, например, к = 8 и попытаться увеличить его впоследствии, если окажется желательным. Если винт поворачивается на полный оборот, то он продви- гается вдоль своей оси на величину Р, равную шагу резьбы. По- скольку на полный оборот затрачивается 2к шагов, то за каждый шаг винт перемещается на deltaL = P Ilk Действительная длина L теперь определится как целое число delta L, примерно равное желаемой длине LQ, так что разность между реальной и желаемой длинами будет меньше половины значения delta L. Очевидно, что обе эти длины будут точно оди- наковыми, если заданное значение желаемой длины LQ окажется равным 1фатному числу delta L. Например, при желаемой длине Ц = 1.5, шаге Р=0.25 и (к =) 6 шагах на пол оборота будем иметь deltaL = 0.25/ (2x6) = 1/48 и L0= 1.5 = 72 х (1/48) то есть в этом случае желаемая длина LQ уже кратна значению delta L, поэтому реальная длина L будет в точности равна LQ. За исключением простановки размерных линий, которые пр- казывают смысл величин Д б/, Л L, оба рисунка 2.21 (а) и 2.21 (б) были напечатаны программой D3D на основе объектного файла, сформированного программой SCRTHR. Ниже приводится текст, появляющийся на экране в процессе формирования дан- ного объектного файла: Большой диаметр: 3 Малый диаметр: 2 Если профиль резьбы должен быть квадратом, то шаг - большой диаметр - малый диаметр - 1.000 Тогда при одном обороте параметр 'шаг' определяет расстояние, на которое перемещается винт, поэтому воображаемый 'квадрат1
2.7. РАСЧЛЕНЕННЫЕ ВИДЫ 101 будет иметь длину стороны h - шаг/2 - 0.500 Желаете ли задать другую величину шага, чтобы профиль резьбы был не квадратным (но прямоугольным)? (Y/N): N Желаемая длина винтовой резьбы (не менее, чем 0.500): 5 "" Число точек на половине оборота винта: 10 Фактическая длина будет - 5.000 Имя выходного файла: FIG2.21 Если бы в ответ на запрос об ином значении шага была нажа- та клавиша У, то программа выдала бы другой запрос, а именно: Шаг: и необходимо было бы ввести значение шага, не зависящее от значений большого и малого диаметров. После генерации файла FIG2.21 указанным образом, можно запустить на выполнение программу D3D, напечатав команду D3DFIG2.21 Для такого сложного объекта, как винт с резьбой вероятно потребуется только перспективное изображение с удаленными невидимыми линиями. В случаях, подобных данному, лучше ис- пользовать только тонкие линии. Может оказаться желательным отобразить граничные грани, которыми аппроксимируются кри- вые поверхности. По умолчанию годится пороговое значение 35е. Рис. 2.21 (а) и (б) были получены таким способом при следующих значениях сферических координат точки наблюдения: (а) р = 1000, 0 = 0°, ^> = 90° (б) р =1000, 0 = 20° ^ = 80° Рис. 2.21(b), полученный при а = 0°, показывает способ ис- пользования плоских граней для аппроксимации кривых поверх- ностей. В параграфе 3.1 этот вопрос будет рассмотрен более под- робно. 2.7. РАСЧЛЕНЕННЫЕ ВИДЫ Кроме видов с разрезом, пример которых показан на рис. 2.11, для сложных объектов можно рекомендовать формирова- ние расчлененных видов. Такой способ особенно полезен в тех случаях, когда объект состоит из различных частей и желательно показать, как их собирать, чтобы образовать полный объект.
102 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ Рис. 2.22. Объект, состоящий из трех частей Рис. 2.23. Расчлененный вид
2.7. РАСЧЛЕНЕННЫЕ ВИДЫ 103 Например, если задано, что объект, показанный на рис. 2.22, состоит из трех частей, то совсем не ясно, как выгладят эти три части, а если они показаны отдельно, то как их собрать вместе. Для этих целей очень полезен расчлененный вид на рис. 2.23 На нем очень ясно видны формы трех частей и предполагается, что нужно только переместить две из них, чтобы получить объект рис. 2.22 в сборе, что и нужно в конечном счете. При желании эти два перспективных изображения читатель может получить самостоятельно, как это сделал автор. В качест- ве внешних размеров каждой из этих трех частей были выбраны числа 40 х 20 * 4 (мм), остальные размеры получаются как след- ствие. Вначале все три части находились в одном и том же поло- жении, с центром в точке начала системы координат. Автор ис- пользовал три отдельных объектных файла для каждой из час- тей, но два объекта, которые на рис. 2.23 изображены на пере- днем плане, можно получить из одного, расположенного на за- днем плане (и являющегося самым сложным) путем исключения из него вершин и изменением набора граней. Затем для получе- ния рис. 2.22 было выполнено несколько поворотов и, наконец, рис. 2.23 был получен путем ряда переносов.
Часть II ПРИКЛАДНЫЕ ПРОГРАММЫ Глава 3 ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D 3.1. ПРОТОТИПЫ ФУНКЦИЙ НА ЯЗЫКЕ СИ До сих пор обсуждалось проектирование в трехмерном про- странстве с точки зрения пользователя. Теперь будем иметь дело преимущественно с программными аспектами. Это означает, что для пользователя, не знакомого с программированием, осталь- ную часть книги читать будет очень трудно. Однако для интере- сующегося пользователя окажется весьма привлекательным, что все обсуждаемые программы приведены в виде исходных текстов и при желании можно внести изменения независимо от автора или обратиться к любому опытному программисту на языке Си за помощью в адаптации программ в соответствии со специаль- ными требованиями. С другой стороны, если читатель заинтере- сован главным образом в аспектах программирования, то было бы серьезной ошибкой пропустить, не читая, главы 1 и 2. Кроме всего прочего, совершенно бесполезно обсуждать как программы что-то делают, не понимая в совершенстве что они делают. Было бы прекрасно, если бы, подобно математике, програм- мирование было бы независимым от машины, поскольку это означало бы, что все написанное нами программное обеспечение существовало бы вечно. На практике очень полезно избегать ма- шинной и компиляторной зависимости везде, где только можно. Но, к сожалению, это не всегда реально, Это в частности отно- сится к программному обеспечению интерактивной графики, которое сильно связано с применяемыми техническими средст-
3.1. ПРОТОТИПЫ ФУНКЦИЙ НА ЯЗЫКЕ СИ 105 вами. Многие из важных тем в этой книге являются зависимыми от системы. В последней фразе был использован сравнительно неопределенный термин ''система" для обозначения комбина- ции компьютера и компилятора. Здесь будут представлены и об- суждены программы на языке Си, работающие на персональных компьютерах или системе PS/2 фирмы IBM. Такой компьютер (или совместимый) должен иметь в своем составе цветной графи- ческий адаптер (CGA), или расширенный графический адаптер EGA (или VGA), или графический адаптер Геркулес (HGA). Эти адаптеры имеют разрешающую способность 640 * 200, 640 * 350 и 720 х 348 соответственно. Если в распоряжении читателя есть совершенно иные технические средства, то вероятнее всего про- граммы из этой книги нельзя будет запустить на выполнение в том виде, в каком они приведены. Однако большинство идей и базовых принципов будут применимы и в других ситуациях, так что можно рассматривать данную реализацию программ на пер- сональном компьютере фирмы IBM только в качестве примера. В этой книге для всех программ будет применяться компиля- тор Турбо Си. Если у читателя возникнет потребность в исполь- зовании другого компилятора языка Си, то придется модифици- ровать лишь несколько подпрограмм нижнего уровня, что не представляет каких-либо серьезных затруднений. В данной книге язык программирования Си используется без пояснений правил его применения. Если потребуются более под- робные сведения по этому языку, чем известны читателю в дан- ный момент, то автор рекомендует обратиться к своей книге "Язык Си для программистов" (а при решении наиболее труд- ных ситуаций может оказаться полезной книга автора "Про- граммы и структуры данных на языке Си", хотя эта последняя книга не годится, если читатель не знаком с основами языка Си). Язык Си хорошо известен своими многочисленными ловуш- ками. Это происходит главным образом из-за отсутствия прове- рок типов и преобразований типов в конкретных ситуациях. На- пример, при записи первой строки определения функции в виде: double f(x) double x; обращение f(l) будет некорректным, поскольку целочисленный аргумент 1 не будет преобразован в тип плавающей точки двой- ной точности. Обращение /(1.0) было бы более правильным, по-
106 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D скольку константа 1.0 имеет требуемый тип. В данном случае отсутствие проверки типа значительно усложняет ситуацию, по- скольку компилятор воспринимает это обращение правильно, а при выполнении программы будет получен неверный результат. Для преодоления затруднений такого типа Американский коми- тет по стандартизации языка Си (ANSI С Standards Committee) предложил определять типы параметров таким образом, что компилятор может выполнить все нужные проверки и преобра- зования. Это предопределяет отличие от исходного языка Си, оп- ределенного Керниганом и Ритчи в^книге "Язык программиро- вания Си". Есть опасение, что принятие стандарта ANSI может привести к различным проблемам переносимости программ. К счастью, большинство компиляторов воспринимают оба стиля записи программ (в стиле Кернигана и Ритчи и предлагаемого стандарта ANSI). В качестве примера такого компилятора назо- вем компилятор фирмы Borland International с названием Турбо Си. В руководстве по Турбо Си уделено довольно много внима- ния этим обоим стилям программирования. По терминологии фирмы Borland говорят, что программы, составленные по ориги- нальным правилам (Кернигана и Ритчи), написаны в классиче- ском стиле, для отличия их от современного стиля в соответствии с правилами стандарта ANSI. Будучи поклонником Кернигана и Ритчи, автор очень удовлетворен такой терминологией. Исход- ный язык Си действительно заслуживает положительной оценки как "классического" языка и совершенно несправедливо назы- вать его "устаревшим". Язык Си становится очень популярным и имеется масса начинающих программистов, для которых даже небольшая помощь со стороны компилятора в поиске ошибок будет чрезвычайно полезной. Воспользуемся примером, чтобы разобраться, о чем речь. В классическом стиле можно записать: /* CLASSIC */ /* КЛАССИЧЕСКИЙ СТИЛЬ*/ double f() /* объявление функции f */ main () { prlntf("%3.ir,f(2.0)); } double f(x) double x; /* определение f */ { return 3.0 * x; }
i. /. ПРОТОТИПЫ ФУНКЦИЙ НА ЯЗЫКЕ СИ 107 В этой программе нельзя заменить 2.0 на 2, поскольку мы при этом получили бы не правильный ответ 6.0, а некоторое непра- вильное значение. В современном стиле мы можем записать: /* MODERN */ /* СОВРЕМЕННЫЙ СТИЛЬ */ double f(double x); /* Прототип функции */ main () { printf("%3.ir,f(2.0)); } double f(double x) /* Определение функции f */ { return 3.0 *х; } Объявление функции / в начале программы является расши- рением ANSI, известным как прототип функции. Компилятору сообщается не только, что функция/возвращает значение типа double (что имеет место и при классическом стиле объявления функции), но и то, что ожидается аргумент такого же типа. Ком- пилятор (воспринимающий современный стиль) проверяет эту ситуацию и при аргументе иного типа либо сообщит об ошибке, либо преобразует заданный тип в требуемый. Компилятор Турбо Си будет нормально воспринимать обе программы CLASSIC и MODERN. Если в последней будет обна- ружена запись /(2), то значение 2 типа int будет автоматически преобразовано в тип double, как если бы в программе было запи- сано 2.0. В этом чрезвычайно простом примере может оказаться совершенно непонятным, почему полезен прототип функции. Однако следует представить, что в больших и сложных програм- мах, а именно такие и будут обсуждаться в данной книге, может потребоваться несколько часов на поиск ошибок преобразования типов, если компилятор не предложит никакой помощи. Большие программы обычно разделяются на несколько моду- лей. Если применить этот модульный принцип к нашему просто- му примеру, то может возникнуть такая ситуация: Модуль 1: /* MODERN */ /* СОВРЕМЕННЫЙ СТИЛЬ */ double f (doudle x); /* Прототип функции f */ main () { prlntf(M%3.1f",f(2.0)); }
108 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D Модуль 2: doublet (double x) { return 3.0 *х; } Когда компилятор обрабатывает Модуль 1, то без прототипа функции он не имел бы никакой информации об определении функции / (то есть о самой функции), поэтому здесь наиболее очевидным образом проявляется полезность включения прото- типа функции. Здесь также нужно заметить, что проверке стоит подвергать не только типы аргументов функции, но и их количе- ство. Если в модуле 1 произвести замену /(2.0) на /( ) или на /(2.06, 3.0, 4.0), то компилятор выдаст сообщение об ошибке, по- скольку прототип функции говорит, что здесь должен быть точно один аргумент. Это было бы невозможно осуществить, если бы прототип функции был заменен строкой double f(); которая удовлетворяет классическому стилю. В этом случае ком- пилятор не выразит неудовольствия, но могут возникнуть серь- езные затруднения во время выполнения откомпилированной и отредактированной программы. Пользователи компилятора Турбо Си версии 1.0, которые пи- шут свои программы в классическом стиле, наказываются, если они не отслеживают очень четко различия между типами float и double. Традиционно аргументы функции типа float "расширя- ются" до типа double. При этом способе любая несовместимость между типами float и double разрешается автоматически, по- скольку даже если параметры заданы типа float, то рассматри- ваемая функция ожидает, что соответствующие аргументы дол- жны быть типа double. Таким образом нормально существует соглашение, что и аргументы и параметры должны иметь тип double всегда, когда они имеют тип с плавающей точкой. К сожа- лению, в компиляторе Турбо Си версии 1.0 имеется отклонение от этого принципа и тип с плавающей точкой для параметров и аргументов воспринимается весьма буквально. Это означает, что при классическом стиле мы встретимся с большими затруднени- ями, если будем комбинировать параметр типа float с константой с плавающей точкой, поскольку константы с плавающей точкой всегда определяются с типом double. При современном стиле
3. /. ПРОТОТИПЫ ФУНКЦИЙ НА ЯЗЫКЕ СИ 109 таких затруднений нет, так как прототип функции строго пред- писывает, как аргументы должны быть преобразованы в тип со- ответствующих параметров. Это представляется автору как один из наиболее сильных аргументов в пользу современного стиля, поскольку вначале не было известно, что это лишь временная проблема. Когда автор получил компилятор Турбо Си версии 1.5 (см. также параграф 4.3), то обнаружилось, что компилятор был скорректирован, и что пользователи классического стиля теперь безопасно могут комбинировать аргументы типа float с парамет- рами типа double и наоборот. Но до сих пор современный стиль дает преимущество тем пользователям, которые еще не замени- ли свои компиляторы (и будет совсем не удивительно, если таких окажется очень много). Наконец, функция с параметрами типа float, при записи в современном стиле, дает объектный модуль, который работает более эффективно, чем объектный модуль для соответствующей программы в классическом стиле, поскольку при современном стиле аргументы не нужно расширять до типа double, а затем снова "сужать" до типа float Эту разницу можно заметить, осуществив раздельную компиляцию двух функций void f (float x) {} /* современный */ void f(x) float x; { } /* классический */ При работе компилятора Турбо Си, версия 1.5, и генерации кодов для процессора 8088 при модели памяти "Huge" длина объектных модулей составляет 143 и 188 байт соответственно, то есть современный стиль дает более компактную программу. Наше обсуждение прототипов функции еще не закончено. Во-первых, имена параметров могут быть опущены, поэтому вместо double f(floatx); можно записать double f(float); Однако, первая форма в большинстве случаев оказывается предпочтительнее, поскольку имена параметров обычно напо- минают для чего служит функция. Во-вторых, может оказаться полезным ключевое слово void, если функция совсем не имеет параметров, то есть можно записать: double f(vold);
110 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D Такая строка заставит компилятор выдать сообщение об ошибке, если встретится обращение к функции/с аргументами. Если бы вместо этого мы записали объявление в классическом стиле double f(); то компилятор будет воспринимать любое количество аргумен- тов и не выдает никакого сообщения об ошибке, по крайней мере относительно числа аргументов, так что ключевое слово void действительно оказывается очень полезным. Ключевое слово void необходимо для указания, что функция не возвращает никакого значения. В этом случае слово void запи- сывается перед именем функции вместо double как в примере: void p (Int I); main () { Р(3); } void p(lnt I) { printfC%dM); } Пропуск слова void в первой строке (прототип функции) при- вел бы к синтаксической ошибке. Если это слово указано в этой строке, то его необходимо также вставлять и в само определение функции, поскольку иначе компилятор будет предполагать, что функция должна возвращать значение типа int (как требуется в исходном языке Си, определенном Керниганом и Ритчи), что противоречило бы прототипу функции. Это означает, что мы могли бы исключить второе появление слова void в этой програм- ме, если одновременно мы изменим его первое появление на сло- во int. Поскольку функция р не содержит оператора возврата, то приведенное выше решение намного изящнее. Компилятор Турбо Си позволяет использовать классический стиль определения функции, даже если ему предшествует объяв- ление прототипа функции, поэтому можно записать, например, Int f (float x); /* прототип функции в современном стиле */ Int f (x) Float x; /* классическое определение функции */ { ... }
3.1. ПРОТОТИПЫ ФУНКЦИЙ НА ЯЗЫКЕ СИ 111 В этом случае проверка на ошибки и преобразование аргу- ментов производится точно таким же образом как и в варианте int f (float x); /* прототип функции в современном стиле */ int f (float x) /* определение функции в современном стиле */ { ... } Смешение стилей, классического и современного, выглядит не особенно элегантно. Если современный стиль используется регулярно, то запись прототипа функции осуществляется про- стым копированием первой строки определения функции за иск- лючением символа точки с запятой, который имеется в объявле- нии прототипа функции, но должен быть опущен в определении функции. Поэтому из двух предыдущих вариантов предпочти- тельнее второй и, начиная с этого момента, будем всегда приме- нять современный стиль. Читатель должен четко представлять, что в Турбо Си всегда используются прототипы функций, поскольку применяются стандартные заголовочные файлы (хндеры), например, хорошо известный файл math.h, включаемый программной строкой: ♦Include <math.h> В компиляторах языка Си классического стиля функция объ- является в этом файле как double atan(); но в компиляторе Турбо Си в файл будет включен прототип функции double atan(double x); Включением файла math.h в программу неявным образом включается прототип этой функции, то же самое относится и к другим заголовочным файлам, так что фактически каждый поль- зователь компилятора Турбо Си применяет прототипы функций, возможно и не зная об этом. Что касается функции atari, то как следствие только что сказанного теперь мы можем записать: pi-4.0*atan(1); вместо pl-4.0*atan(1.0);
112 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D чтобы вычислить приближенное значение числа л, поскольку компилятор Турбо Си осуществит преобразование целого числа 1 в число с плавающей точкой двойной точности. Но в целях обеспечения переносимости программ, более целесообразно при- менять последнюю форму записи, нежели предыдущую. Можно считать недостатком, что современный стиль не по- зволяет применять компиляторы, воспринимающие только про- граммы, написанные в классическом стиле. Но автор перешел на современный стиль после тщательного изучения всей проблемы в целом. Автор написал пять книг (одну на голландском языке и четыре на английском), в которых применяется классический стиль, но после всестороннего рассмотрения вопросов, обсуждав- шихся в данном параграфе, автор отказался от классического стиля и полностью переключился на современный стиль. Если читатель желает сделать то же самое, то автор рекомендует быть последовательными в этом решении. Кроме того, компилятор Турбо Си может помочь, выдавая сообщение об ошибках, если были пропущены прототипы некоторых функций. В "интегрированной системе" это реализуется путем выбора опций Options, затем компилятора Compiler, затем ошибок Errors, и, наконец, "более редкие ошибки" Less Common errors. Здесь нужно включить опции "обращение к функции без прото- типа" {Call to functions with no prototypes) и "нет объявления для функции" (No declaration for functions). Автор в своей систе- ме включает также и другие опции из меню: "болеередкие ошиб- ки" и "общие ошибки" (Less common errors), поскольку при этом включаются такие полезные ситуации, как сигнализация о включении переменной, к которой никогда не обращаются. Прототип функции фактически действует как объявление функции, которое является более полным, чем оно записывается при классическом стиле. Как и в объявлениях классических фун- кций, их можно группировать вместе и даже комбинировать их с программными переменными, такой как х в примере: float х, f(int i, char ch), g(void); Эта строка эквивалентна трем строкам float x; float f(lntl, char ch); \ float g(vold);
3. 7. ПРОТОТИПЫ ФУНКЦИЙ НА ЯЗЫКЕ СИ ИЗ Необходимо также помнить, что если в некотором программ- ном модуле функция определена в современном стиле и в том же самом модуле все обращения к этой функции появляются только после ее определения, то отдельное включение прототипа функ- ции было бы избыточным. Вот пример такого определения: float f(float x) /* Определение функции в современном стиле */ { return x * х + х+1; } main() { printf("%f\f(1)) } Благодаря современному стилю целочисленный (int) аргу- мент "1" преобразуется в тип float, даже если нет отдельно про- тотипа для функции/. Необходимость прототипа функции не от- носится к случаям, подобным приведенному примеру, где опре- деление функции уже выполняет роль прототипа функции. Если пользоваться советами автора об инструкциях компиля- тору Турбо Си относительно вывода сообщений в случаях пропу- ска прототипов функций, то лучше не допускать таких прототи- пов. Это относится и к "стандартным" функциям, таким как print/ и exit. Прототипы для этих функций включены в заголо- вочные файлы и при использовании функции print/ (что имеет место почти всегда), программа должна начинаться со строки: #include<stdio.h> Аналогичным образом функция exit объявляется в заголовоч- ном файле process.h. Теперь должно быть ясно, почему имеется так много строк "include" в программах в данной книге. Напом- ним, что при классическом стиле не было необходимости вклю- чать заголовочный файл для функций, возвращающих значения типа int или не возвращающих никаких значений. Но теперь это нужно обязательно и такая дополнительная работа очень оправ- дана, поскольку мы всегда страдаем от человеческой особенности делать ошибки. Например, при современном стиле и при вклю- ченном заголовочном файле stdio.h неправильный оператор printfCA'); вызовет сообщение об ошибке, тогда как в случае классического стиля эта серьезная ошибка записи 'Л' вместо "Л" привела бы к
114 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D очень неприятным последствиям во время выполнения програм- мы из-за того, что значение кода ASCII для 'Л' (65) было бы использовано как начальный адрес строки, которая должна быть отпечатана. Вот и все, что касается компилятора Турбо Си и аспектов языка программирования. Поскольку в эту книгу включено очень много текстов программ, автор полагал, что было очень по- лезно детально обсудить эти важные Подробности, прежде чем приступить к описанию самих программ. 3.2. ЦИЛИНДРЫ И ПРИЗМЫ Рассмотрим теперь сравнительно простые программы, фор- мирующие файлы, которые затем будут считываться програм- мой D3D. Сначала это будет программа CYLINDER для форми- рования цилиндра. Она рассматривалась ранее в параграфе 2.2 с пользовательской точки зрения, а теперь разберем сам исходный текст программы, чтобы впоследствии аналогичные программы можно было составлять самостоятельно. /* CYLINDER: */ /* Программа генерирует данные для построения цилиндра */ /* Program to generate a cylinder */ ♦include <stdio.h> ♦Include <math.h> #lnclude<ctype.h> ♦include <process.h> main() { FILE*fp; charch, str[50]; float diam, r, h, delta, alpha, pi, rcos, rsfn, half; intl.ll.n; pi-4*atan(1.0); do { printf /* "Direction of axis? (X/Y/Z) */ ("Направление оси? (X/Y/Z)u); ch - getchar(); ch - toupper(ch); } while (ch !-'X1 &&ch !- 'Y* && ch!-'Z'); printf/* "Diameter?" */ ("Диаметр?"); scanf("%f\ &diam); r-diam/2; printf /* "Altitude?" */ ("Высота? u); scanf("%f",&h);half-h/2; printf /* "Number of polygon edges?" */ ("Число вершин полигона? u); scanf("%d", &n);
3.2. ЦИЛИНДРЫ И ПРИЗМЫ 115 prlntf /* "Name of output file?" */ ("Имя выходного файла? "); scanfC%sH, str); fp-fopen(str, "w"); if (fp - - NULL){printf /* "File problem" */ ("Проблема с файлом"); exlt(1);} delta-2*pi/n; for(i-1;K-n;i++) { alpha-i* delta; rcos - r * cos(alpha); rsin - r * sln(alpha); if(ch — *Z*) { fprintf(fp, "%d %f %f %f\nM, I. rcos, rsin, half); fprlntf(fp, "%d %f %f %f\n", l+n, rcos, rsin, -half); } else if(ch--'X') { fprintf(fp, "%d %f %f %f\n", i, half, rcos, rsin); fprintf(fp, "%d %f %f %f\n", i+n, -half, rcos, rsin); } else /*ch--'Y'*/ { fprlntf(fp, "%d %f %f %Лпи, I, rsin, half, rcos); fprintf(fp, "%d %f %f %f\n", l+n, rsin, -half, rcos); } } fprintf(fp, "Faces- Грани:\пи); for(l-1;i;i++) fprintf(fp,"%d%sM, 1--п?"Лпп:1%10?"":"\пи); for(i-1;i;i++) fprlntf(fp,"%d%s'\2*n+1-i, l--n?".\n":l%10?,",:"\nw); for(i-1;K-;i++) { 11-H-n? 1:1+1; fprintf(fp, "%d %d %d %d.\n", I, i+n, 11+n, 11); } fclose(fp); Напомним, что в качестве основания используется правиль- ный многоугольник (полигон) вместо окружности, то есть фак- тически мы имеем дело с призмой вместо цилиндра. Из этого сле- дует, что программа может быть использована не только для ци- линдра, но и для любой призмы, если ее основанием является правильный многоугольник. Это демонстрируется на рис. 3.1, на котором показано правило нумерации вершин. Такой оператор программы как fprintf(fp,"%d%sM, l«-n?".\nM:i%10?"":"\n"); первоначально может показаться непонятным. В нем содержится
116 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ DID Рис. 3.1. Призма как результат работы программы CYLINDER вложенный условный оператор в виде: ррр ? ааа : qqq ? bbb : ссс который интерпретируется следующим образом: ррр ? ааа : (qqq ? bbb : ссс) В некоторых других исходных текстах программ вместо такой формы будем использовать обычные условные выражения, улуч- шающие читаемость программ, в данном примере это привело бы к следующей последовательности: if (i - - n) fprintf(fp," %d.\n", i); else if (i % 10-1-0) fphntf(fp, "%d ", i); else fprintf(fp,a%d\n,,,i); To есть, после вывода на печать значения числа / будет выве- дена точка и символ перевода строки, если / равно /г; при нера- венстве будет выведен пробел, если только число i не кратно 10, в этом случае будет выполнен перевод строки. Как в исходной форме, так и в этой расширенной версии получим до десяти но- меров вершин в одной строке. \
3.2. ЦИЛИНДРЫ И ПРИЗМЫ 117 Остальной исходный текст программы вполне ясен, если его рассматривать в сочетании с изображением рис. 3.1. Напомним, что нумерация вершин должна быть ясной в первую очередь, если желательно разобраться в работе программы, подобной дан- ной или при самостоятельном написании таких программ. Ниже приводится пример диалога при формировании приз- мы, изображенной на рис. 3.1: cylinder Направление оси? (X/Y/Z) z Диаметр? 10 Высота? 15 Число вершин полигона? 4 Имя выходного файла? fig3.1 После этого можно ввести команду d3dfig3.1 а затем, используя команду М, указать, что нужна нумерация вершин и изображение осей, которые должны быть отражены на весь используемый размер экрана, после чего с помощью коман- ды Р полученный результат может быть напечатан так, как он показан на рис. 3.1. Заметим, что стороны нижнего и верхнего квадратов являются хордами окружности, которая получилась бы при выборе большего количества сторон. В этих квадратах диагонали (а не стороны) будут иметь длину 10, равные как бы "диаметру", указанному в процессе диалога. Поскольку файл FIG3.1 очень небольшой, то его можно при- вести здесь полностью: 1 -0.000000 5.000000 7.500000 5 -0.000000 5.000000 -7.500000 2 -5.000000 -0.000000 7.500000 6 -5.000000 -0.000000 -7.500000 3 0.000000 -5.000000 7.500000 7 0.000000 -5.000000 -7.500000 4 5.0000000.000001 7.500000 8 5.000000 0.000001 -7.500000 Faces-Грани: 1234. 8765. 1562. 2673. 3784. 4851.
118 Глава J. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D 3.3. КОНУСЫ И ПИРАМИДЫ Программа CONE формирует конус, аппроксимируемый пи- рамидой, что совершенно аналогично аппроксимации цилиндра призмой, что было показано в предыдущем параграфе. /*CONE: Generating a cone. Программа генерирует данные для построения конуса V #lnclude<math.h> #include<stdio.h> ♦include <process.h> main() { FILEMp; intn, I; float diam, r, h, delta, alpha, x, y, pi; charstr[50]; pl-4.0*atan(1.0); printf /* "Diameter of base circle?" */ ("Диаметр окружности основания?"); scanf("%f", &diam); r-diam/2; printf /* "Altitude?" */ ("Высота?"); scanf("%f", &h); printf /* "Number of polygon edges?" */ ("Число вершин полигона?"); scanf("%d", &n); printf /* "Name of output file?" */ ("Имя выходного файла? "); scanf("%s", str); delta -2*pi/n; ^ . fp - fopen(str, "w"); if (fp - - NULL) {printf /* "File problem" */ ("Проблема с файлом"); exit(1);} for(i-1;i<-n;i++) { alpha-1 * delta; x - r * cos(alpha); у - r * sin(alpha); fprintf(fp, "%2d %f %f %f\n", i, x, y, 0.0); } fphntf(fp, "%2d %f %f %f\n", n+1,0.0,0.0, h); fprintf(fp, "Faces- ГраниЛп"); for(i-n; i>-1; I—)fprintf(fp. "%d%s", i, (i--1 ? "An" :(!%10--1?"\n":""))); for(i-1;i<-n;i++) fphntf(fp,"%d%d%d.\n", i,(i--n?1:i+1),n+1); fclose(fp); } Продемонстрируем процесс диалога при работе этой програм- мы для^генерации файла, считываемого затем программой D3D для формирования изображения, показанного на рис. 3.2:
3.3. КОНУСЫ И ПИРАМИДЫ 119 Диаметр окружности основания? 10 Высота? 15 Число вершин полигона? 30 Имя выходного файла? fig3.2 В данном случае не все номера вершин на рис. 3.2 оказались написанными четко, поскольку буквы jc, у, z вблизи концов поло- жительных координат полуосей выводились после этих номеров и на видеодисплее буквы дс, у, z частично перекрыли номера 4,1 и 5 соответственно. В версиях изображений, выводимых на печать, номера вершин часто не отображаются, но если они нужны, то необходимо позаботиться об исключении осей, тогда не будут выписываться и буквы х, у, z, а все номера вершин будут изобра- жены без искажений. Ради полноты приведем также содержимое файла FIG3.2: 1 -0.000000 5.000000 0.000000 2 -5.000000 -0.000000 0.000000 3 0.000000 -5.000000 0.000000 4 5.000000 0.000001 0.000000 5 0.000000 0.000000 15.000000 Рис. 3.2. Пирамида как результат работы программы CONE
120 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D Faces- Грани: 4321. 12 5. 2 3 5. 345. 415. Конечно, слишком расточительно писать специальную про- грамму для генерации такого простого файла. Но если нужна хо- рошая аппроксимация конуса, как в параграфе 2.2, то программа CONE окажется действительно полезной. 3.4. ТРАДИЦИОННАЯ АППРОКСИМАЦИЯ СФЕРЫ В параграфе 2.2 были применены два способа аппроксимации сферы — с помощью программы SPHERE и с применением фай- ла SPH80.DAT. Первый способ рассматривается в этом парагра- фе. В программе SPHERE определяются т горизонтальных слоев между двумя полюсами и п линий долготы (меридианов — наи- больших окружностей, проходящих через полюса). Вместо зна- чений т = 10 и п - 20, применявшихся в параграфе 2.2, для при- мера лучше задавать небольшие значения, чтобы четко изобра- зить номера вершин. При т = 4 и п = 8 программа SPHERE, и вслед за ней D3D дает результат, показанный на рис. 3.3. Рис. 3.3. Аппроксимация сферы при тг4 и п-8
3.4. ТРАДИЦИОННАЯ АППРОКСИМАЦИЯ СФЕРЫ 121 Использованный нами способ основан на хорошо известных соотношениях между прямоугольными координатами х, у, z и сферическими координатами R, 0, <р для всех точек на поверхно- сти сферы с радиусом Лис центром в точке О: х = R sin <р cos в y = Rsin<p sin0 z = R cos <p Линии параллелей (лежащие в горизонтальных плоскостях) получаются заданием постоянной <р и переменной в; аналогично при постоянной в и переменной <р получим линии меридианов — наибольших окружностей, проходящих через полюса. В программе SPHERE используется целочисленная перемен- ная i\ которая изменяется от 0 до т и вершины нумеруются свер- ху вниз следующим образом: i = 0 (северный полюс) : 1 i= 1 (первая параллель): 2, 3,..., л+1 i = 2 (вторая параллель): п+2, п+3,..., 2я+1 i-3 (третья параллель): 2л+2, 2гс+3,..., Зя+1 i = т-\ (последняя параллель): (т-2)л+2, (w-2)ai+3, ..., (т-1)л+1 i = m (южный полюс): (т-1)/г+2 При аппроксимации плоских граней получим треугольники вблизи полюсов и четырехугольники на остальной поверхности (более точно эти четырехугольники являются трапециодами, то есть имеют две параллельные стороны). Имея в виду эту инфор- мацию, программа SPHERE должна быть понятной. /♦SPHERE: */ /* m slices, n points on each circle */ /* сфера содержит m слоев, на каждой окружности п точек */ #include<stdio.h> #lnclude<math.h> ♦Include <process.h> main() { FILE*fp; Int I. j, m, n, nr, next, southpole; double r, theta, phi, pi, delphi, deltheta, rslnphi, rcosphi, x, y, z; char str[30];
122 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D pi-4*atan(1.0); printf /* "Enter m, the number of horizontal slices: " */ ("Введите число m - числу горизонтальных слоев:"); scanf("%d",&m); printf /* There will be n points on each horizontal circle. */ . ("\nHa каждой горизонтальной окружности будет п точек.\n"); printf /* It is recommended to choose n about twice as large asm.V ("Рекомендуется выбирать n примерно в два раза больше т.")', .printf /* Enter n */ ('ЛпВведите число n: "); scanf("%d", &п); delphl - pi/m; deltheta-2*pi/n; printf /* "Radius of the sphere:"*/ ("\nРадиус сферы:u); scanf("%lf", &r); printf /* "Name of output file?" */ ("Имя выходного файла?"); scanf("%s", str); fp-fopen(str, "w"); if (fp-« NULL) {printf/* "File problem"*/ ("Проблема с файлом"); exit(1);} /* Vertex numbering: Нумерация вершин: К): 1 i-1: 2,3 n+1 i-2: n+2,n+3 2n+1 i-m-1: (m-2)n+2, (m-2)n+3 (m-1)n+1 i-m: (m-1)n+2 */ fprintf(fp."%d%f%f%f\n", 1,0.0.0.0. r); /*i-0 */ for(i-1;i<m; i++) { phi-l * delphi; rcosphl - r * cos(phi); rsinphi - r * sin(phi); for(j-0;j<n;j++) \ { nr-(M)*n + j + 2; theta-j*deltheta: x- rsinphi * cosftheta); у-rsinphi * sln(theta); z- rcosphl; fphntf(fp. *%d %f %f %f\n". nr. x. y. z); } } fprintf(fp. "%d %f %f %f\n". (m-1)*n+2, 0.0,0.0.-r); /*i-m */ fprintf(fp. "Faces- ГраниЛп"); for(j-2;j<-n+1;J++) fprintf(fp, "%d %d %d.\n", 1, j,(j<n+1 ? J+1:2)); for(M;i<m-1;i++) { nr-(i-1)*n;
3.5. ПРАВИЛЬНЫЕ МНОГОГРАННИКИ 123 for(j-2;j<-n+1;J++) { next-Q<n+1?j+1:2); fphntf(fp,M%d%d%d%d.\nM, nr+j, nr+n+j, nr+n+next, nr+next); } } southpole-(m-1)*n+2; nr-(m-2)*n; for(|-2;j<-n+1;j++) fprintf(fp,"%d%d%d.\n", nr+j, southpole, (j<n+1 ? nr+j+1: nr+2)); fclose(fp); } 3-5. ПРАВИЛЬНЫЕ МНОГОГРАННИКИ Многогранником называется сплошное тело у которого пло- ские ограничивающие грани являются полигонами. Если все сто- роны полигона имеют одинаковую длину, а все внутренние углы равны между собой, то такой полигон называется правильным. Аналогично и многогранник называется правильным, если у него все грани определяются идентичными полигонами. Существует только пять правильных многоугольников, иногда называемых также "Платоновыми телами". Они показаны на рис. 3.4. Их ос- новные характеристики приведены в следующей таблице: Многогранник Тетраэдр Гексаэдр Октаэдр Додекаэдр Икосаэдр Грани 4 6 8 12 20 Ребра 6 12 12 30 30 Вершины ' 4 8 6 20 12 Следует упомянуть, что эти числа удовлетворяют теореме Эйлера, утверждающей, что для любого многогранника имеем соотношение Грани + Вершины в Ребра + 2 Рис. 3.4 был получен с помощью программы D3D. Здесь пока- заны пять многогранников в том же порядке, как они названы в таблице. Для додекаэдра и икосаэдра, показанных в нижнем ря- ду, для генерации объектных файлов были использованы отдель- ные программы. Эти программы приведены в параграфах 3.5.4 и 3.5.5. Рассмотрим поочередно все пять правильных многогран- ников более детально.
124 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D Рис. 3.4. Пять правильных многогранников: тетраэдр, гексаэдр, эктаэдр, додекаэдр, икосаэдр 3.5.1. Тетраэдр Хотя тетраэдр имеет всего четыре грани (в виде равносторон- них треугольников), вычерчивание его трехмерной проекции со- всем не тривиальная задача. Простейший способ построения тетраэдра заключается в использовании куба, как показано на рис. 3.5. Здесь грани вводится командой F> за которой следуют четыре строки для определения четырех треугольных граней: 13 6. 386. 16 8. 18 3. Эта картинка не очень хороша из-за того, что у тетраэдра нет горизонтального основания. К счастью, объект можно поворачи- вать вокруг любой оси (с помощью команды Т) для улучшения этой ситуации. В качестве такой оси можно использовать ребро 1-3 для поворота тетраэдра в сторону наблюдателя так, чтобы вершина 6 двигалась вниз до совпадения с плоскостью ху, тогда треугольник 1-3-6 займет горизонтальное положение. Факти- чески была использована ось, проходящая через точки 9 и 10, также отмеченные на рис. 3.5. Эта прямая линия проходит через
3.5. ПРАВИЛЬНЫЕ МНОГОГРАННИКИ 125 Рис. 3.5. Тетраэдр в кубе точку начала системы координат О и, в то же время, является центром тетраэдра. Следовательно, этот центр будет фиксиро- ванной точкой поворота. Другими словами, после поворота вок- руг оси 9-10 центр тетраэдра по-прежнему останется в точке О, что в нашем примере значительно упрощает задачу переноса объекта в новую позицию на следующем этапе (например при формировании рис. 3.4). Угол а, на который следует осуществить поворот, показан на рис. 3.6. Это угол с вершиной в точке 11 прямоугольного треу- гольника с вершинами 11, 2 и 6. Его противолежащая сторона (ребро куба 2-6) имеет единичную длину, а его прилежащая сто- рона (половина диагонали грани куба) имеет длину 1/2V7. Тогда будем иметь: tga=l / (I/2V2") = VI Используя простейший калькулятор, можно найти a = arctgV2 =54.73561° Значение этого угла необходимо для выполнения нужного по- ворота. После удаления точек 5, 7, 2, 4 будет получен тетраэдр, изображенный в качестве первого объекта на рис. 3.4.
126 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D Рис. 3.6. Угол а и ось 9-10 используемые при повороте 3.5.2. Гексаэдр Чтобы согласовать название с именами других многогранни- ков, будем использовать слово гексаэдр для объекта, который обычно называется кубом. Конечно, нам уже известно, как скон- струировать куб с помощью программы D3D, так что нет необхо- димости снова подробно обсуждать такую задачу. Напомним, что мы можем использовать программу D3D для построения двух отдельных объектов, а затем загрузить их по одному, применяя операцию переноса к одному из них для изме- нения положения относительно друг друга, как это делалось в параграфе 2.2. Таким образом можно сформировать рис. 3.4, по- очередно определяя все пять объектов и совмещая их на одном рисунке только после того, как будут получены пять объектных файлов. В каждом из этих пяти файлов центр объекта располо- жен в точке начала системы координат О. Таким же образом точ- ка О может быть использована в качестве фиксированной точки для операций масштабирования и при этом объект будет масшта- бироваться просто раздуваясь или сжимаясь, оставляя центр не- подвижным. Поэтому лучше всего операцию масштабирования выполнять сразу же после загрузки рассматриваемого объекта из его объектного файла и только после этого выполнять перенос.
3.5. ПРАВИЛЬНЫЕ МНОГОГРАННИКИ 127 3.5.3. Октаэдр Как видно из рис. 3.4, две вершины октаэдра расположены по обе стороны квадрата. Припишем номера вершин так, как пока- зано на рис. 3.7 и будем говорить, что стороны квадрата 1-2-3-4 имеют единичную длину. Точка 7 расположена в центре квадра- та (и также является центром октаэдра), а точка 8 находится посредине ребра 4-1. Поскольку точка 5 лежит на перпендикуля- ре в точке 7, то все, что нам надо знать, это расстояние h между этими двумя точками. Здесь можно использовать тот факт, что все вершины правильного многогранника находятся на одинако- вом расстоянии от центра. Следовательно, треугольник 1-5-7 на рис. 3.7 является равнобедренным прямоугольником, так что точка 7 находится на одинаковом расстоянии как от точки 5, так и от точки 1, а последнее расстояние равно половине диагонали квадрата 1-2-3-4 (с единичной длиной стороны). Следователь- но, если точка 7 находится в начале нашей системы координат, а точка 5 лежит на положительной полуоси х, то координата z то- чек 5 и 6 будет соответственно 1/2VT и -УтП. Очень полезно проверить, что грани сконструированного таким образом объекта действительно являются равносторон- ними треугольниками. Для этого сначала применим теорему Рис. 3.7. Октаэдр
128 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D Пифагора к треугольнику 8-7-5 для нахождения длины отрезка прямой линии 8-5. После возведения в квадрат длин сторон 8-7 и 7-5 сумма этих квадратов равна (1/2)2 + (1/2^2)2 = 3/4 По теореме Пифагора эта сумма равна квадрату длины отрезка прямой линии 8-5. Прибавим эту величину к квадрату длины отрезка прямой линии 8-1 и найдем 3/4+ (1/2)2= 1 что, опять-таки в соответствии с теоремой Пифагора, должно быть длиной отрезка 1-5. Итак, все ребра объекта имеют единич- ную длину. Это означает, что все восемь ограничивающих треу- гольников нашего объекта одинаковы и этот объект несомненно является октаэдром. Поскольку известно, что ViV7! « 0.707107, то для октаэдра рис. 3.7 может быть использован следующий объектный файл (без точек 7 и 8): 1 0.5 0.5 0.000000 2 -0.5 0.5 0.000000 3 -0.5 -0.5 0.000000 4 0.5 -0.5 0.000000 5 0.0 0.0 0.707107 6 0.0 0.0-0.707107 Faces-Грани: 12 5. 235. 34 5. 415. 162. 263. 364. 461. 3.5.4. Додекаэдр Как было упомянуто в начале параграфа 3.5, додекаэдр имеет 12 граней, 30 ребер и 20 вершин. Каждая из 12 граней является правильным Пентагоном, то есть пятиугольником с пятью равны- ми сторонами. Как уже видели на рис. 1.12 в параграфе 1.5 доде- каэдр вписывается в куб и это свойство можно использовать для его конструирования.
3.5. ПРАВИЛЬНЫЕ МНОГОГРАННИКИ 129 Рис. 3.8. Пентагон Перед использованием пятиугольника для построения доде- каэдра рассмотрим сначала свойства самих пятиугольников. Один такой пятиугольник показан на рис. 3.8, его центр нахо- дится в точке Ё. Будем говорить, что все пять вершин пятиуголь- ника лежат на окружности с единичным радиусом, центр кото- рой расположен в точке Е. На рис. 3.8 показаны некоторые важные углы. Из них и из условия EG - R = 1 найдем BE = со$ 36° BG = sin 36° Угол в 36° (=л/5 радиан) является одним из интереснейших, поскольку 2x36° = 72° 1/2x36° = 18° 72°+ 18° = 90° Это позволяет записать значение cos 36° в виде очень просто- го выражения cos36° = (l+V5)/4 5—273
130 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D Как показал Д.Е. Кнут в своей книге "Искусство программи- рования", том 1, упражнение 19 в параграфе 1.2.8, доказать это выражение можно с помощью подстановки и = cos 72°, ve cos 36°. Тогда будем иметь: w = 2cos236°-l=2v2-l v=l-2sin218° = l-2cos272° = l-2a2 Следовательно Деление на и + v дает и + v = 2(v2 - и2) = 2(v + и) (v- и) l=2(v-w)=2{v-(2v2-l)}=-4v2 + 2v + 2 Нужно решить квадратное уравнение 4v2-2v-l=0 что и дает желаемый результат v=(l+V5)/4 На самом деле будем использовать еще более важную константу t = (1+V5)/2= 1.6180339887... то есть будем иметь cos 36° = т/2 Замечательное свойство параметра т заключается в том, что воз- ведение в квадрат дает точно такой же результат, как и увеличе- ние на единицу: т2 = (1 + V5)2/4 - 11/2 + Vri5 так что имеем т2 = т + 1 „ (3.1) Путем умножения обеих половин этого уравнения на степень переменной г, найдем последовательность выражений т3 = т2 + т т4 = т3 + г2 т5 = И + г3
3.5. ПРАВИЛЬНЫЕ МНОГОГРАННИКИ 131 Аналогично, если поделим выражение (3.1) на степень т, получим т-1+Г1 Г2шГ3 + т-4 Таким образом, каждый элемент последовательности т-3 -2-1 , 2 3 является суммой двух предшествующих элементов. Из всего этого следует, что если отрезок прямой линии разде- лить на две части так, что более длинная часть в т раз длиннее короткой, тогда целый отрезок в т раз длиннее длинного отрезка. Это соотношение известно как "золотое сечение". Другое важное,свойство константы т заключается в том, что любая ее степень может быть записана в виде суммы ai + Ь, где а и b — целые числа: т2 = т+1 т3 = т2 + т = 2т = 1 И = т3 + т2»Зт + 2 т5 = т4 + т3 = 5т + 3 и так далее. Также имеем т-|-т-1 Г2=1-т-1=2-т т"3 = г_1-т"2 = 2т-3 т-4 = т"2-Г3-5-Зт т-5 = Г3-Г4 = 5т-8 " и так далее. На основании (3.1) можно легко проверить равенство (от + b)(at - Ь- а) = а2- ab- b2 где предполагается, что если а и Ъ одновременно не равны нулю, то имеем: 1 _ сп-Ь-а 2 2 az + b a -ab-b
132 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D Таким образом, любое частное P(t)/Q(t), где Р(т) и Q(t) — многочлены для переменной т с рациональными коэффициента- ми, может быть записано в линейной форме at + ft, причем а и Ь — рациональные числа. Это делает т очень удобной константой для работы, поскольку есть хороший шанс упростить сложные выражения, если эта константа в них встречается. Все это может показаться отклонением от темы исследования пятиугольника, но это не так. Вот один из примеров — любые две диагонали пятиугольника, пересекающиеся во внутренней точке делят одна другую в отношении золотого сечения! Мы этого использовать не будем, но, как следует из рис. 3.8, если в пяти- угольнике провести некоторые дополнительные линии, то будет образовано много углов в 36°, 72°, 18° и 54° (= 36° + 18°). Теперь можно записать с = cos 36° = т/2 s = sin 36° = V(l - с2) = V(l - т2/4)~ = V{1 -(т+ 1)/4} - i/2V(3 - т) cos 72° = 2с2- 1 - 1/2Т2- 1 = 1/2(т + 1) - 1 = (т- 1)/2 и, используя буквы с А до F, как показано на рис. 3.8, будем иметь: AB = AE + EB=l+cos36°= 1+т/2 EF = EDxcos72° = (t-1)/2 6=BF = BE + EF = cos36°+cos72° = t/2 + (t-1)2 = t-i/2 а = CF - FD = ED х sin72° = 1 х 2 х Cos36° x sin36° - is (при этом будем иметь в виду, что в выражениях ts значение 5 можно заменить на i/2V(3 — т). Теперь рассмотрим рис. 3.9, который представляет собой вид спереди на додекаэдр в кубе, который был изображен на рис. 1.12 в перспективе. Здесь отрезок АВ, отмеченный также на рис. 3.8, изображен в своей натуральной величине 1 + т/2. Он представ- ляет собой гипотенузу прямоугольного треугольника АВН, кото- рый можно будет использовать для вычисления длины половины ребра А куба. На основании теоремы Пифагора имеем: АН2 + ВН2 = АВ2 (A-s)2 + A2=(1+t/2)2 2A2-sA + s2=(1+t/2)2 ,
3.5. ПРАВИЛЬНЫЕ МНОГОГРАННИКИ 133 2A2-2sA+(3-t)/4=(1+t + t2/4 2A2-2sA+(3-t)/4=(1+t+(t + 1)/4 4A2-4sA-3t-1=0 A= [4s + V{16s2+16(3t+ 1)Г ]/8 = [4s + V{4(3-t) + 16(3t+1)} ]/8 = {2s + V(11t + 7) }/4 что можно упростить на основе соотношений т4 = Зт + 2 т6 = 8т + 5 Тогда получим V(11t + 7) = V(t° + г4) = г2 V(r2 + 1) = (т + 1)V(t + 2) и окончательно 2s + (r+l)V(7^27 Л= 4 Получив этот результат, мы знаем как размер куба, так и по- ложение точек А и В на нем. Нам необходимо также знать по- зиции в трехмерном пространстве точек С и D, показанных на рис. 3.8. Они лежат на горизонтальной линии, проходящей через точку F, показанной как на рис. 3.8, так и на рис. 3.9, поэтому сначала найдем положение точки F. Из рис. 3.9 имеем v_ h Ъ т/2+1 следовательно 2bh 2(T-V2)h . 1Ч, v=—гт"= Го—= <т~ DA т + 2 т+2 Последнее равенство может быть проверено вычислением произведеният+2ит-1с привлечением уравнения (3.1). Используя оба эти выражения для v и d h-s v A (см. рис. 3.9), получим </=(t-1)(A-s)
134 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D Теперь легко найти положения точек С и D (см. рис. 3.8), по- скольку они лежат на прямой линии, перпендикулярной плоско- сти рис. 3.9 и проходящей через точку F, а длина отрезков CF и DF была вычислена ранее как а - rs. Благодаря симметрии позиции всех остальных точек вершин додекаэдра могут быть определены из только что найденных зна- чений. Все это подробно показано в программе DODECA, кото- рая генерирует объектный файл как для додекаэдра, так и для куба, изображенных на рис. ЗЛО. Координатные оси х, у, z в этой иллюстрации опущены, чтобы избежать дополнительных слож- ностей в рисунке, но они имеют обычное направление, то есть они совпадают с направлениями ребер 103-102, 101-102 и 102- 106 соответственно, а начало системы координат расположено в центре куба. Вершина 2 на рис. ЗЛО соответствует точке А на рис. 3.8 и рис. 3.9 и так далее. Можно легко исключить изображе- ние куба, удалив вершины с номерами в диапазоне 101-108, если нужно изобразить только додекаэдр. Такая потребность возник- ла, например, при объединении данного файла с другими пра- вильными многогранниками, показанными на рис. 3.4. Рис. 3.9. Додекаэдр в кубе (вид спереди)
3.5. ПРАВИЛЬНЫЕ МНОГОГРАННИКИ 135 А 08— Рис. 3.10. Нумерация точек в додекаэдре и кубе /*DODECA: /* This program constructs a dodecahedron and a cube in which it fits. /* Эта программа формирует описание додекаэдра и куба, /* в который он вписывается ♦include <stdio.h> #lnclude<math.h> maln() { floats,a, h, v, d, tau; static float x[21],y[21],z[21]; intl; /* Initially О /* Первоначально О */ V FILE*fp; tau-(sqrt(5.0)+ 1)/2; /* tau-2 * cos(pl/5) */ /* Sometimes the letter phi is used Instead of tau */ /* Иногда применяется обозначение phi вместо tau s-sqrt(3-tau)/2; a-tau*s; h - (2*s-Ktau+1)*sqrt(tau+2))/4; /*s-sln(pl/5) /* Half side of cube /* Половина стороны куба v-(tau-1)*h; d-(tau-1)*(h-s); printf /* "The twelve faces of the dodecahedron are" */ ("Двенадцать граней додекаэдра представляют собой\п");
136 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D printf /* "pentagons, each fitting in a circle with radius 1." */ ("пятиугольники, вписанные в окружности с радиусом 1.\п"); printf /* "The sides of these pentagons have length */ ("Стороны этих пятиугольников имеют длину %f.\n", 2*s); printf /* "The dodecahedron fits in a cube with edges of length" */ ("Додекаэдр вписывается в куб с длиной стороны"); printtt" %f.\n",2*h); x[5]-x[7]-s; х[6]-х[8]— s; x[9]-x[10]-h; x[11]-x[12]--h; x[13]-x[15]-x[17]-x[19]-a; x[14]-x[16]«x[18]-x[20]--a; У[1]-У[3]—s; yP]-y[4]-s; y[5]-y[6]-h; y[7]-y[8]—h; y[13]-y[14]-y[17]-y[18]-h-d; y[15]«y[16]-y[19]-y[20]—(h-d); z[1]-z[2]-h; z[3]-z[4]—h; z[9]-z[11]-s; z[10]-z[12]--s; z[13]-z[14]-z[15]-z[16]-v; z[17]-z[18]-z[19]-z[20]--v; fp-fopen("dodeca.dat", "w"); for(i-1;i<-20;l++) fprintf(fp, "%3d %f %f %f\n", i, x[i], y[i], z[l]); /* The cube in which the dodecahedron fits will have the vertex numbers 101,102 108: Куб, в который вписывается додекаэдр, будет иметь вершины с номерами 101,102 108 V ntf(fp, "101 %f %f %f\n". h,-h,-h); ntf(fp, "102 %f %f%f\n", h, h,-h); ntf(fp, "103 %f %f %f\n", -h, h,-h); ntf(fp, "104 %f %f %f\n", -h, -h, -h); ntf(fp,"T05%f%f%f\n", h,-h, h); ntf(fp, "106%f%f%f\n", h, h, h); ntf(fp, "107%f%f%f\n",-h, h, h); ntf(fp. "108 %f %f %f\n", -h, -h, h); ntf(fp, "Faces- ГраниЛп"); ntf(fp, " 1 15 9 13 2An"); ntf(fp, "1 2 141116.\n"); ntf(fp, "5 6 14 2 13.\n"); ntf(fp, "7 15 1 16 8.\n"); ntf(fp, "19 10 9 15 7An"); fpri fpri fpri fpri fpri fpri fpri fpri fpri fpri fpri fpri fpri
3.5. ПРАВИЛЬНЫЕ МНОГОГРАННИКИ 137 fprintf(fp, "1017 5 13 9.\гГ); fprintf(fp, "20 816 11 12ЛгГ); fprintf(fp, "18 12 11 14 бЛгГ); fprintf(fp,u3 4 171019.\n"); fprlntf(fp," 4 18 6 517.\n"); fprintf(fp, "320 12 18 4.\nM); fprlntf(fp," 3 19 7 820.\гГ); /* The cube edges are entered as loose line segments: */ /* Ребра куба описываются в виде отдельных отрезков прямых: */ fprlntf(fp, "101 Ю2.\п102 ЮЗЛпЮЗ 104.\п104 101Лпи); fprlntf(fp. "105 Ю6.\п106 Ю7.\п107 108.\п108 Ю5.\пп); fphntf(fp, "101 Ю5.\п102 ЮбЛпЮЗ Ю7.\п104 108Лпи); fclose(fp); } 3.5.5. Икосаэдр Как видно из рис. 3.11, икосаэдр можно разместить таким об- разом, что десять его вершин будут также вершинами двух гори- зонтальных пятиугольников. Две оставшиеся будем считать се- верным полюсом (наверху) и нижним полюсом (внизу), а пря- мую линию, проходящую через эти полюса будем называть осью. Центр О икосаэдра будет находиться в точке начала системы координат, а ось икосаэдра совпадает с осью z. Два только что упомянутых пятиугольника будут иметь точно такие же разме- ры, как грани додекаэдра, описанного в параграфе 3.5.4. То есть каждый из них, имеет единичный радиус описанной окружнос- ти, а стороны пятиугольника будут иметь длину 25, где s = sin 36° Каждая сторона этого пятиугольника в свою очередь является ребром икосаэдра, поэтому длина этих ребер также равна 2s. Из рис. 3.11 очевидно, что нижний горизонтальный пятиугольник может быть получен из верхнего путем поворота его на угол 36° вокруг оси и переноса по вертикали. Это означает, что остается только решить задачу нахождения координат по оси z для обоих пятиугольников и обоих полюсов. На рис. 3.12 изображен верх- ний пятиугольник из показанных на рис. 3.11; только для обоз- начения вершин вместо номеров 2, 3, 4, 5, 6 будем использовать буквы А, В, С, D, Е. Точка F является центром пятиугольника ABCDE, а точка О — центр всего икосаэдра, буквой N обозначен северный полюс, точка М расположена в центре отрезка CN. Те- перь нам нужно найти размеры Лиг, обозначенные на рис. 3.12.
138 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D Рис. 3.11. Нумерация точек в икосаэдре г 1 1 1 1 1 1 0 / ^Ч^ £- / ^^^^Ч^ь -л / 1 ^^"^ / в Рис. 3.12. Пятиугольник и северный полюс икосаэдра
3.5. ПРАВИЛЬНЫЕ МНОГОГРАННИКИ 139 Поскольку треугольник NFC является прямоугольным, то можно применить теорему Пифагора для определения значения А = FN, выраженного через s (=sin 36°): FN2 = CN2-CF2 A2-(2s)2-l2 Снова подставляя т = (1 + V3)/2 (см. параграф 3.5.4), а также используя S=l/2V(3-T) s2=(3-t)/4 значение Л можно также выразить через т: Л = V(2 - т) = V(t~z) = г-1 = т - 1 Чтобы найти r= ON, используем подобие треугольников NMO и NFC. Отсюда будем иметь г - 2s что дает 2s2 (3-т)/2 ,. Координата z для всех точек в пятиугольнике ABCDE равна г-Л=(т-1/2)-(т-1) = 1/2 Это означает, что оба пятиугольника расположены на единич- ном расстоянии друг от друга или, другими словами, расстояние между ними равно радиусу описанной окружности для пяти- угольника. Возвращаясь к нумерации вершин в икосаэдре, показанном на рис. 3.11, обнаружим, что для вершин 2, 3, 4, 5,6 координата z равна 0.5 (= г - А). Аналогично имеем z = -0.5 для вершин 7,8,9, 10, 11. Для обоих полюсов (точки 1 и 12) z-координаты будут равны г = г - 1/2 и -г = -(г - 1/2) соответственно и для этих то- чек х=у=0. Что касается координат х и у для вершин 2,..., 11, то для их определения можно использовать тот факт, что все они лежат на горизонтальной окружности с радиусом 1. То есть при
140 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D соответствующих углах а они равны cos а и sin а соответственно. Угол а может быть выбран следующим образом: Вершина Угол а 2 -36° 3 36° 4 108° 5 180° 6 252° 7 0° 8 72° 9 144° 10 216° 11 288° Эти данные используются в программе ICOSA, текст которой приведен ниже. Программа генерирует объектный файл, поз- воляющий программе D3D сформировать рис. 3.11. /* ICOSA: Icosahedron: a regular polyhedron with 20 boundary */ /* faces, which are equilateral triangles. */ /* Икосаэдр: правильный многогранник с 20 гранями, каждая /* из них представляет собой равносторонний треугольник. */ #include<stdlo.h> #lnclude<math.h> #deflne p(l, x, у, z) fprlntf(fp,n%d %f %f %An",l,x,y,z) #deflne face(l, j, k) fprlntf(fp, "%d %d %d.\n", I, j, k) mainQ { FILE*fp; inti; double s, pi, r, alpha, tau; pi-4*atan(1-0); tau-(sqrt(5.0)+ 1)/2; /* tau-2 * cos(pi/5; */ s-sin(pi/5); /* pi/5 radians-36 degrees */ r - tau - 0.5; /* pi/5 радиан - 36 градусов */ printf /* "Ten of the twelve vertices of the icosahedron" */ ("Десять из двенадцати вершин икосаэдра лежат на двух\п"); printf /* "lie on two horizontal pentagons, either of which" */ - ("горизонтальных пятиугольниках, каждый из которых\п"); printf /* "fits in a circle with radius 1." */ ("вписывается в окружность с радиусом 1.\п"); printf /* "These two pentagons lie a distance 1 apart." */ ("Оба эти пятиугольника расположены на расстоянии 1.0\п"); printf /* "The edges have length " */ ("друг от друга. Ребра имеют длину %f.\n", 2*s); printf /* "The icosahedron fits in a sphere with radius" */ ("Икосаэдр вписывается в сферу с радиусом %f.\n", г);
3.6. АППРОКСИМАЦИЯ СФЕРЫ 80 ТРЕУГОЛЬНИКАМИ 141 fp-fopen("icosa.dat", "w"); р(1,0.0, ОД г); /* North pole */ /* Северный полюс */ for(i-0;i<5; i-к-) { alpha--pi/5 + i* pi/2.5; /* Или в градусах (In degrees): -36 +1 * 72 */ p(2+l. cos(alpha), sln(alpha), 0.5); } for(i-0;i<5;l++) { alpha-i* pi/2.5; p(7+l, cos(alpha), sln(alpha), -0.5); } p(12,0.0,0.0, -r); /* South pole */ /* Южный полюс */ fprintf(fp, "Faces- ГраниЛп"); for (Ю; l<5; i++) face(1,2+I, l<4 ? 3+1:2); for (i-0; l<5;!++) face(2+i, 7+i, l<4 ? 3+I:2); for(i-0;i<5;i++) face(7+i, l<4 ? 8+i: 7, l<4 ? I+3:2); for (i-0; i<5; I++) face(i+7,12, i<4? I+8:7); } Вероятно следует упомянуть, что существует и другой способ построения икосаэдра. Этот очень изящный способ основан на использовании трех взаимно перпендикулярных прямоугольни- ков, аналогичных трем частям объекта на рис. 2.22. В обозначе- ниях рис. 3.11 ими могут быть, например, прямоугольники: 2-6-9-8, 1-4-12-11, 3-7-10-5 Это так называемые золотые прямоугольники: размеры их сторон находятся в соотношении 1: т. Если расположить эти пря- моугольники параллельно осям координатной системы, то поло- жение икосаэдра будет отличаться от показанного на рис. 3.11. Автор настоятельно рекомендует использовать этот способ и в качестве упражнения самостоятельно написать программу, гене- рирующую такой икосаэдр. 3.6. АППРОКСИМАЦИЯ СФЕРЫ 80 ТРЕУГОЛЬНИКАМИ Пять правильных многоугольников имеют слишком мало граней для того, чтобы использовать их для аппроксимации сфе- ры. Однако можно будет использовать икосаэдр в качестве осно- вы для другого объекта, имеющего 80 треугольных граней. Такой объект был упомянут в параграфе 2.2. и он изображен на рис. 2.9 ирис. 2.10.
142 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D Рис. 3.13. Треугольник, разбитый на четыре более мелких треугольника Как видно из рис. 3.13, треугольник может быть разбит на четыре более мелких треугольника, используя средние точки каждой стороны и соединяя соседние точки между собой. Непос- редственное применение этого способа к каждому из 20 равно- сторонних треугольников икосаэдра было бы неверным, посколь- ку при этом фактически не было бы увеличено количество плос- костей, в которых располагаются ограничивающие грани. Вместо самих средних точек D, E, F (показанных на рис. 3.13) будем использовать их центральные проекции на поверхность сферы, которую предстоит аппроксимировать. Пусть эта сфера имеет радиус 1 и центр в точке О (в точке начала системы коор^ динат). Тогда вершины А, В, С больших треугольников будут располагаться на расстоянии 1 от точки О, но точки D, E, F будут находиться на несколько меньшем расстоянии d от точки О. Продлим отрезки OD, OE, OF (имеющие длину d) для получения таких отрезков OD', OE\ OF\ что длина всех этих трех отрезков будет равна 1. Пусть, например, имеем VVrf zD' = zD/d Вместо средних точек D, E, F будем использовать точки D', E', F\ а вместо исходного треугольника — четыре более мелких тре- угольника AF'E', F'BD', E'D'C, F'D'E'.
3.6. АППРОКСИМАЦИЯ СФЕРЫ 80 ТРЕУГОЛЬНИКАМИ 143 Программа SPH80 основана на программе ICOSA из парагра- фа 3.5.5. Сначала вычисляются те же двенадцать вершин, но значения их координат делятся на коэффициент г, чтобы апп- роксимировать единичную сферу (вместо сферы с радиусом г). Поскольку каждая сторона большого (равностороннего) треу- гольника является также стороной соседнего треугольника, то каждую среднюю точку пришлось бы вычислять дважды, если не принять специальных мер. Поэтому будем отслеживать все реб- ра, средняя точка для которых уже вычислена, так что все сред- ние точки появятся в объектном файле только однажды. Осталь- ные детали будут ясны из исходного текста программы /*SPH80: Многогранник с 80 треугольными гранями для аппроксимации сферы. Он базируется на икосаэдре, который является правильным много- гранником с 20 равносторонними треугольными гранями. Вместо каждого из этих треугольников используются четыре меньших тре- угольников, что дает 20 х 4 - 80 малых треугольников (но уже не равносторонних). Для каждого равностороннего треугольника икоса- эдра средняя точка каждой из трех сторон проецируется на сферу, .в которую вписан икосаэдр. Четыре маленьких треугольника получа- ются путем соединения соседних точек отрезками прямой линии, */ #include<stdio.h> #lnclude<math.h> #include <process.h> void polnt(int i, float x, float y, float z); void subdlvlde(int A, Int B, int C); void storeface(lnt I, Int J, Int k); void mldpnt(lnt B, Int C, Int *pP, float x, float y, float z); intnpoints; struct {float x, y, z;} pnt[43]; Int nfaceK); struct{lntl,J,k;}fc[80]; IntnedgeK); struct {Int I, J, P;}edges[120]; FILE*fp; main() { Int I, I; , double pi, r, alpha, tau; pl-4*atan(1.0); tau - (sqrt(5.0) + 1)/2; /* tau - 2 * cos(pi/5) */ r-tau-0.5; prlntf /* "A sphere (with radius 1) Is approximated " */ ("Сфера (с радиусом 1) аппроксимируется объектомЛп");
144 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D printf ("ограниченным 80 треугольными гранями. Результат\гГ); printf ("записывается в выходной файл SPH80.DAT, который\п"); printf ("затем может быть прочитан программой D3D.\n"); fp-fopen("SPH80.DAT'\ "w"); polnt(1,0.0,0.0,1.0); /* Северный полюс */ for(i-0; i<5; i++) { alpha--pi/5+i* pi/2.5; /* Или в градусах : -36 + i * 72 */ point(2+i, cos(alpha)/r, sin(alpha)/r, 0.5/r); } for(i-0;i<5;i++) { alpha - i * pi/2.5; point(7+i, cos(alpha)/r, sin(alpha)/r, 4D.5/r); } point(12,0.0,0.0. -1.0); /* Южный полюс */ npoints-12; for (i-0; i<5; i++) subdivide(1.2+i, l<4 ? 3+i: 2); for (i-0; i<5; i-н-) subdivide(2+i. 7+I, i<4 ? 3+i:2); for (i-0; i<5; i++) subdivide(7+i, i<4 ? 8+i: 7, i<4 ? i+3: 2); for(i=0; i<5; I++) subdivide(i+7, 12, i<4? i+8 : 7); fprintf(fp, "Faces- ГраниЛп"); for(M);l<80;l++) fprintf(fp, "%d %d %d.\n", fc[l].i, fc[l].j, fc[l].k); fclose(fp); > void midpnt(int B, int C, int *pP, float x, float y, float z) /* Точка (x, y, z) — средняя точка отрезка ВС.Если это новая вершина, */ /* то она запоминается и записывается в объектный файл данных с */ /* новым номером вершины. Если нет, то находится ее номер вер- */ /* шины. Номер вершины должен быть приписан *рР в любом случае. */ { inttmp, e; if (С < В) {tmp - В; В - С; С - tmp;} /* В и С в порядке возрастания ради унификации */ for(e-0; e<nedge; е-н-) if (edges[e].i - - В && edges[e].j - - С) break; if(e--nedge) /* He обнаружено, значит имеем новую вершину */ { edges[e].i-B; edges[e].j - С; edges[e]. Р - *рР - -и-npoints; nedge-н-; point(*pP,x, y.z); } else *pP - edges[e].P; /* Ребро ВС уже рассматривалось ранее, следовательно */ /* вершина не новая */ }
3.6. АППРОКСИМАЦИЯ СФЕРЫ 80 ТРЕУГОЛЬНИКАМИ 145 void point(int i, float x, float y, float z) { fprlntf(fp, "%d %f %f %f\n", i. x, y, z); pnt[i].x - x; pnt[l].y - y; pnt[l].z - z; void subdlvide(lnt A, Int B, Int C) /* Деление треугольника ABC на четыре малых треугольника */ { float xP, yP, zP, xQ, yQ, zQ, xR, yR, zR; IntP.Q, R; static float d—1; /* Значение переменной d равно -1 только до первого обращения */ /* к этой функции; после первого вызова переменной d будет */ /* присвоено точное значение (между 0 и 1), равное расстоянию */ /* между любой средней точкой и центром сферы. Все средние */ /* точки проецируются на сферу (с радиусом 1) путем деления */ /* на d значений их координат. */ xP - (pnt[B].x + pnt[C].x)/2; yP-(pnt[B].y+pnt[C].y)/2; zP-(pnt[B].z + pnt[C].z)/2; xQ - (pnt[C].x + pnt[A].x)/2; yQ - (pnt[C].y + pnt[A].y)/2; zQ - (pnt[C].z + pnt[A].z)/2; xR - (pnt[A].x + pnt[B].x)/2; yR - (pnt[A].y + pnt[B].y)/2; zR - (pnt[A].z + pnt[B].z)/2; if(d<0)d-sqrt(xP*xP + yP*yP + zP*zP) /* 0<d<1 */ xP/-d; yP/-d; zP/-d; xQ/-d; yQ/-d; zQ/-d; xR/-d; yR/-d; zR/-d; mldpnt(B,C, &P,xP,yP,zP); mldpnt(C, A, &Q, xQ, yQ, zQ); mldpnt(A, B, &R, xR, yR, zR); _ storeface(A, R, Q); storeface(R, В, Р); storeface(Q, P, C); storeface(Q, R, P); void storeface(int i, Int j, Int k) { fc[nface].l-l; fc[nface].jej; fc[nface].k-k; nface++;
146 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D Рис. 3.14. Вид сверху на многогранник с 80 гранями Мы уже видели несколько перспективных изображений скон- струированного объекта (см. рис. 2.9 и рис. 2.10). На рис. 3.14 по- казан вид сверху на этот объект. Напомним, вершиной с номером 1 обозначен северный полюс; в этой проекции можно видеть вер- шины пяти исходных треугольников 1-2-3, 1-3-4, 1-4-5, 1-5-6, 1-6-2 и как каждый из этих треугольников заменен на четыре более мелких треугольника. Только что рассмотренный принцип может быть распростра- нен на полученный объект еще раз. Это означает, что количество треугольных граней снова увеличивается в четыре раза и сфера будет аппроксимирована 320 треугольниками. Это будет хоро- шим упражнением для самостоятельной разработки программы! 3.7. ПОВОРОТ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ В параграфе 1.8.1 уже обсуждались некоторые пользователь- ские аспекты выполнения операции поворота в трехмерном пространстве. Теперь наступило время разобраться с программ- ным обеспечением, с помощью которого может быть реализован такой поворот. С точки зрения программиста операция поворота будет выполняться с помощью двух функций initrotate и rotate. Первая из них применяется для детального описания информа- ции об операции поворота, в результате ее работы генерируется
3.7. ПОВОРОТ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ 147 матрица поворота. Это выполняется только однажды для каждо- го поворота, независимо от количества точек, которые должны быть повернуты. Вторая функция выполняет фактические дейст- вия, она вызывается для каждой поворачиваемой точки. Чтобы определить поворот необходимо задать направляю- щую ось и угол а. После этого поворот на заданный угол а будет выполняться относительно указанной оси. Для направляющей оси будем использовать точку А с координатами jca, уА, zA и век- тор v- [vp v2, v3] Тогда направляющей осью будет прямая линия, проходящая через точку А с направлением вектора v. Если поворачивать (нормальный) винт в том же направлении, как и наш поворот на угол а (предполагая, что угол а положителен), то винт будет пе- ремещаться вдоль оси в направлении вектора v. Функции initrotate и rotate использовать очень легко. Рас- смотрим в качестве примера программу GENROTA, которая является общей программой для любого поворота в трехмерном пространстве: она считывает объектный файл (в формате про- граммы D3D) и записывает аналогичный выходной файл. Имена этих файлов, точка А, вектор v и угол а вводятся с клавиатуры. /* GENROTA: V /* Общая программа для поворота набора точек. */ /* После компиляции должны быть отредактированы */ /* связи совместно с модулем TRAFO.OBJ. */ #include<stdio.h> #include<math.h> #lnclude <process.h> void initrotate(double xA, double yA, doubje zA, double v1, double v2, double v3, double alpha); void rotate(double x, double y, double z, double *px1, double *py1. double *z1); main() { FILE*fp1,*fp2; double xA, yA, zA, v1, v2, v3, alphadeg, alpha, x, y, z, x1.y1.z1, pi; char str[30]; int nr. ch. n-O;
148 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D printf/* "Input file:" */ ("Входной файл: "); scanf("%s", str); fp1-fopen(str, V); printf/* "Output file:" */("Выходной файл:"); scanf("%s", str); fp2-fopen(str, "w"); If (fp1--NULL I I fp2--NULL) { printf /* "File problem" */ ("Проблема с файлом"); exlt(1); } printf /* "Enter xA, yA, zA:" */ ("Введите значения параметров xA, yA, zA:"); scanf("%lf %lf %lf", &xA, &yA, &zA); printf/* "Enter v1,v2,v3:"*/ ("Введите значения параметров v1, v2, v3:u); scanf("%lf %lf %lf", &v1, &v2, &v3); printf /* "Enter alpha (In degrees):" */ ("Введите значение угла поворота (з градусах):"); scanf("%lf", &alphadeg); pi-4*atan(1.0); alpha —alphadeg * pi/180; /* alpha in radians */ /* угол alpha в радианах */ initrotate(xA, yA, zA, v1, v2, v3, alpha); while (fscanf(fp1, "%d %lf %lf %lf", &nr, &x, &y, &z)--4) { rotate(x, y, z. &x1, &y1, &z1); n++; fprintf(fp2, "%d %f %f %f\n", nr, x1, y1, z1); } while (ch - getc(fpl), ch !- EOF) putc(ch, fp2); fclose(fp1); fclose(fp2); printf/* "Ready! %d points rotated" */ ("Готово! %d точек повернуты!", п); Для демонстрации воспользуемся призмой, описываемой сле- дующим объектным файлом: 1 1.000000 0.000000 0.000000 2 1.0000002.000000 0.000000 3 0.000000 2.000000 0.000000 4 0.000000 0.000000 0.000000 5 1.000000 0.000000 3.000000 6 1.000000 2.000000 3.000000 7 0.000000 2.000000 3.000000 8 0.000000 0.000000 3.000000 Faces-Грани: 1265. 2376. 3487. 1485. 5678. 1432.
3.7. ПОВОРОТ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ 149 При хА = 0.5, уА = 1, zA = 0, Vj = 0, v2 = 0, v3 = 1, а = 75° этот файл преобразуется в новый файл, который описывает ту же са- мую призму, но повернутую на 75° вокруг ее вертикальной оси. Обе призмы, исходная и повернутая, изображены на рис. 3.15. Рис. 3.15 Призма до и после поворота Программа GENROTA должна быть связана с откомпилиро- ванным модулем TRAFO.OBJ. Если читатель никогда не пользо- вался редактором связей в Турбо Си для связи модулей между собой, то он должен знать, что для этой цели можно использовать так называемый "проектный файл" {"project file"), например GENROTA.PRJ; который в нашем примере будет содержать только две строки текста: GENTRAFO TRAFO Затем нужно выбрать опцию Р и ввести имя проектного фай- ла и все будет работать. Мы будем использовать модуль TRAFO в нескольких применениях, так что весьма полезно скомпилиро- вать его лишь однажды. Следующий модуль с именем файла TRAFO.C компилируется и результирующему объектному фай- лу присваивается имя TRAFO.OBJ. После того, как это сделано однажды снова компилировать его уже не придется, не нужно дублировать и исходный текст во все программы, включающие операцию поворота.
150 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D /*TRAFO: Преобразования в трехмерном пространстве */ #include<math.h> double r11. П2, r13, r21, r22, r23, г31,г32,гЗЗ.г41,г42,г43; vpid initrotate ( double a1, double a2, double аЗ, double v1, double v2, double v3, double alpha) /* Вычисление матрицы поворота I r11r12r13 0 I I r21r22r230 I R - I r31r32r330 I I г41г42г43 1 I Способ использования (см. функцию 'rotate'): [х1 у1 z1 1]-[x у z 1]R Точка (x1.y1.z1) является отображением точки (х, у, z). Поворот на угол 'alpha' осуществляется относительно оси (а1,а2,аЗ) + lambda (v1,v2,v3). */ { double rho, theta, cal, sal, cph, sph, cth, sth, cph2, sph2, cth2, sth2, pi, call; cal - cos(alpha); sal - sln(alpha); cal1-1.0-cal; rho - sqrt(v1*v1+v2*v2+v3*v3); pi-4.0*atan(1.0); If (rho - - 0.0) {theta-0.0; cph-1.0; sph-0.0;} else { if(vl--O.O) theta - (v2 >- 0.0 ? 0.5*pi: 1.5*pi); else { theta-atan(v2/v1); If(vKO) theta-и-pj; } cph - v3/rho; sph - sqrt(1.0 - cph*cph); } /*cph-cos(phl), sph-sln(phl) */ cth - cos(theta); sth - sln(theta); cph2-cph*cph; sph2 - 1.0-cph2; cth2 - cth*cth; sth2 -1.0 - cth2; r11 - (cal*cph2+sph2)*cth2+cal*sth2; r12 - sal*cph+cal 1 *sph2*cth*sth; r13-sph*(cph*cth*cal1-sal*sth); r21 -sph2*cth*sth*cal1-sal*cph; r22 - sth2*(cal*cph2+sph2)^cal*cth2; r23 - sph*(cph*sth*cal 1+sal*cth); r31 -sph*(cph*cth*cal1+sal*sth); r32-sph*(cph*sth*cal1-sal*cth); r33-cal*sph2+cph2;
3.7. ПОВОРОТ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ 151 г41-а1-а1*М1-а2*г21-аЗ*г31 г42 - а2-а1*М2-а2*г22-аЗ*г32 г43 - аЗ-а 1 *г13-а2*г23-аЗ*гЗЗ void rotate ( double x, double у, double z, double *px1, double *py1, double *pz1 ) { *px1 - x*r1 1+y*r21+z*r31+r41 *py1 - X*r12+y*r22+z*r32+r42 *pz1 - X*r13+y*r23+z*r33+r43 } Если читатель знаком с матричным умножением, то после- дующее пояснение поможет более четко представить работу функций initrotate и rotate. Последняя из них вычисляет вектор отображения х' путем умножения исходного вектора х на общую матрицу поворота ^GEN: х' = х/?г где *GEN х = [х у z 1 ] У z' 1 ] X' = [*' VGEN 12\ Г31 Lr41 Г12 Г13 °" г22 г23 О Г32 Г33 ° Г42 Г43 1 Задача функции initrotate заключается в вычислении этой матрицы как произведения других матриц: VGEN Т l R* Т где Т — матрица переноса, которая содержит координаты задан- ной точки А: "10 0 0 1 0 0 0 1 L*A ?A ZA [10 0 0 1 0 0 0 1 Г*А^А-2А 0 0 0 1 0 0 0 1
152 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D Перед выполнением поворота объект переносится к точке О матрицей Т~ , которая также является матрицей переноса. За- тем выполняется поворот на угол а вокруг оси, проходящей через точку О (в направлении вектора v), который описывается матри- цей R* и, наконец, повернутый объект переносится обратно с помощью матрицы Т. Нам еще предстоит обсудить матрицу R*. Эта матрица с размером 4x4 получается из матрицы 3x3 путем добавления единичного вектора-столбца и единичного вектора- строки, что символически можно записать как Д* = 0 0 0 где девять точек представляют элементы матрицы R> которую теперь предстоит определить. Она находится как произведение пяти других матриц 3x3: R = R. -1 -1 -г -у Wz Средняя из этих матриц, Ry, выполняет поворот на угол а: cos a sin а 0] |-sina cos a 0 0 0 1 *v = Как немедленно следует из этой матрицы, матрица Ry выпол- няет поворот вокруг оси z, тогда как нужно определить поворот вокруг направляющей оси, проходящей через точку О в направ- лении вектора v. Этим объясняется включение четырех других матриц: они обеспечивают преобразования систем координат, которые вкратце обсудим ниже. Во-первых, нам потребуются сферические координаты кон- цевой точки (Vj, v2, v3) вектора v (предполагая, что начальная точка этого вектора находится в точке О). Почти в самом начале этой книги на рис. 1.2 был показан смысл сферических коорди- нат р, 0, <р. Они связаны с координатами концевой точки вектора v следующими соотношениями: vl =p sin <p cos в v2 =p sin в sin (p v3=pcos<p
3.7. ПОВОРОТ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ 153 К сожалению, нам нужно определять не координаты Vj, v2, v3 из /о, 0, <р, а совсем наоборот. Потребуются более сложные обратные формулы и их можно определить следующим образом: Если уо = 0 (что не должно встречаться), то будем полагать 0 = (р = 0. Иначе arctan(v2/Vj) если v{ > 0 л + arctan(v2/vj) если v{ < 0 ж/2 Зтг/2 0 = если Vj = 0 и v2 ^ 0 если Vj = 0 и v2 < О <Р :arccos (v3/r) *z= Теперь можно изменить координатную систему, так чтобы вектор v, определяющий ось вращения, совпадал с новой поло- жительной осью z. Начнем с поворота осей х и у вокруг оси z на угол 0. Если такому преобразованию подвергнута точка, то мат- рица поворота должна быть cos0 sin0 0] -sin 0 cos 0 0 0 0 lj Но поскольку мы будем использовать преобразование системы координат, то потребуется инверсия этой матрицы: "cos0 -sin0 0] sin0 cos0 0 _ 0 0 lj (Это условие можно проверить на простом примере в двухмер- ном пространстве: перемещение оси у на расстояние d вправо изменяет координаты х всех точек таким же образом, как если бы все точки были перенесены на расстояние d влево). На следующем шаге повернем (теперь уже новые) оси х и z вокруг (новой) оси у на угол />, для чего потребуется матрица cos^> 0 sin^>" 0 1 0 |_-sin^> 0 cos^J Это дает новую ось z, совпадающую по направлению с направле- нием вектора v, поэтому теперь можно использовать матрицу /?у, V- V-
154 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D V показанную выше. После этого координатная система должна быть преобразована обратно в свое исходное состояние. Для это- го сначала воспользуемся первой матрицей |~cos^> 0 -sin^>] 0 10 [sin у? 0 cos^>J а затем матрицей Rz, упомянутой при обсуждении матрицы /? ~ . В принципе, все действия по вычислению матрицы Rz, как произведения пяти заданных матриц могли бы быть выполнены компьютером, но поскольку все эти вычисления пришлось бы де- лать с числами вместо операций с выражениями, то при этом пришлось бы проделать массу ненужной работы. Кроме того, окончательная матрица R проще промежуточных результатов, и если можно получить матрицу R из 0, <р и а, то ее можно исполь- зовать сразу же, а не выполнять все эти матричные умножения каждый раз. Поэтому такая работа выполнена автором самым обычным образом, с использованием больших листов бумаги и с придумыванием обозначений вроде sa для sin а, чтобы слишком длинные формулы записывать в разумных размерах. При этом оказалось возможным сделать очень много упрощений. Резуль- тат этой работы заложен в функцию initrotate, включенной в мо- дуль TRAFO. 3.8. КРИВЫЕ ТИПА В-СПЛАЙНА В параграфе 2.4 была использована программа CURVE3 для аппроксимации последовательности точек в трехмерном прост- ранстве. Рассмотрим существо самой программы вместе с обсуж- дением методики формирования В-сплайнов, на которой она основана. Очень рекомендуется снова прочитать параграф 2.4. При т заданных точках имеется т - 1 интервал, но только т - 3 из них соответствуют участкам кривой В-сплайна. Будем называть такой участок сегментом кривой или более общим названием — сегментом. Например, на рис. 3.16 т = 10 и вся кри- вая состоит из 10-3 = 7 сегментов, соответствующих отрезкам прямых 2-3, 3-4, 4-5,5-6, 6-7, 7-8, 8-9. Трудность в аппроксимации кривой типа В-сплайна заклю- чается в том, что нельзя отличить один сегмент от другого, поскольку вся кривая очень гладкая! Из рис. 3.16 можно видеть,
3.8. КРИВЫЕ ТИПА В-СПЛАЙНА 155 что первый сегмент начинается вблизи точки 2, так что он дол- жен заканчиваться где-то вблизи точки 3, где начинается второй сегмент, но где этот переход совершается, из кривой совершенно не видно. Но всегда необходимо помнить, что такие граничные точки существуют, поскольку иначе было бы невозможно понять применяемые математические основы. В частности, рис. 3.16 был получен следующим образом. Сна- чала программа D3D использовалась для задания точек с 1 по 10 и вычерчивания девяти соединяющих отрезков прямых линий. Для этой цели была применена проекция, которую можно было бы назвать вид спереди, что означает установку очень большого значения/9, в = 0° и <р = 90°. Управляя курсором, были определе- ны десять точек в плоскости yz. Затем к объектному файлу, скажем с именем PNT10.DAT, была применена программа CURVE3 и введено число N- 20 как количество интервалов меж- ду каждыми двумя последовательными точками. Этот результат был записан в объектный файл, который затем был снова считан программой D3D. Наконец, по команде R и без стирания изобра- жения на экране, был загружен исходный файл PNT10.DAT, чтобы отобразить десять исходных точек и отрезки прямых ли- ний между ними. Только номера точек были вписаны вручную, поскольку иначе были бы изображены номера всех 141 промежу- точных точек на кривой, что сильно перегрузит изображение. Рис. 3.16. Точки, аппроксимированные кривой типа В-сплайна
156 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ (Этот пример показывает, что программа для подбора кривой в трехмерном пространстве может быть использована для подбора кривой и в двухмерном пространстве — десять задаваемых точек и результирующая кривая лежат в плоскости yz). Что касается используемых математических соотношений, упомянутых выше, то для этой цейи применяются полиномы третьей степени в параметрической форме: з 2 О "3S ' "2% ' "1" "О (3.2) Поскольку существует т - 3 сегментов кривой, как говори- лось выше, то будем иметь т - 3 наборов кубических уравнений (каждый "набор" состоит из трех уравнений отдельно для каж- дой из координат дс, у, z). В каждом сегменте кривой, лежащем между точками i и i: +1, переменная t изменяется от 0 до 1, то есть при t = О имеем конечную точку вблизи точки *, а при t = 1 произ- водится аппроксимация точки / + 1. Для каждого такого сегмента кривой (между точкой i и точ- кой i + 1) можно вычислить коэффициенты а» Ьр с. (/=0, 1,2, 3) по значениям координат четырех соседних точек, а именно точек i - 1, i, i + 1, i + 2. Чтобы упростить обсуждение и дать практиче- скую базу, последующие выражения для вычисления коэффици- ентов В-сплайна приводятся без вывода: а3 = а0 = *3 = *0 = с3 = с2 = СГ С0 = -хы+ЗхгЗхм+х1+2)/6 xM-2Xi + xm)/2 -хм+х1+1)/2 хы+4Х1 + хт)/6 yhl-2yi + yi+l)/2 ум+ут)П >н+4у/ + 3'ж)/б -zM+3z.- zHl-2zi + zM)/2 rzM+zM)/2 z._1+4z/ + zm)/6 3z*i+W/6 (3.3) Заметим, что все три набора из четырех уравнений подобны.
ЗЛ КРИВЫЕ ТИПА В-СПЛАЙНА 157 В уравнениях (3.2) значения координат дс, у, z определяются полиномами от переменной 7, а из (3.3) находим коэффициенты этих полиномов. Ради полноты и имея в виду последующее об- суждение вопроса в параграфе 3.9, следует упомянуть, что если в (3.2) заменить коэффициенты я3 и так далее на соответствую- щие выражения изг:истемы (3.3) и выполнить группировку чле- нов в результирующих выражениях, то получим систему х - F.x (0 хы + F0(t) xt + Fx (0 хм + F2(t) xi+2 y = F_{(t) yhl+FQ(t) y^Fx(t) У^+F^t) yi+2 (3.4) z = F_x (t) zhl + FQ(t) zt + Fx (t) zM + F2(t) zi+2 в которой введены обозначения стыковочных функций: ^1(0 = Ы3 + 3^2-3^+1)/6 F0(0 = (3f*-6*2 + 4)/6 ^(0 = (-3^3 + Зг2 + Зг+1)/6 U,:>> F2(t) = t3/6 В программе CURVE3 значения координат х, уу z вычисляют- ся по формулам (3.2), что более эффективно, чем применение эквивалентных выражений (3.4). Однако из последних более четко видно, что каждая новая точка (х, у, z) вычисляется как линейная комбинация заданных соседних точек и что коэффи- циенты (то есть стыковочные функции) зависят от t. /* CURVE3: Подбор кривой В-сплайна в трехмерном пространстве. программа считывает входной файл и записывает данные в выходной файл. Оба эти файла совместимы с програм-мой D3D, поэтому может быть использована схема: D3D —> входной файл —> CURVE3 —> выходной файл —> D3D Входной файл содержит текстовые строки с номерами точек i и трехмерные координаты m точек P(i) (i - 1,2 m). Остальная часть файла, начинающаяся со слова 'Faces', игнорируется. Программа записывает в выходной файл трехмерные координаты к точек Q(j) (j - 1,2 к), причем каждой тройке координат предшествует номер точки j и параметр к определяется как к - (т-3) * N + 1. Значение N вводится с клавиатуры (число N соответствует количеству интервалов между двумя последовательными точками P(i) и P(i+1). Точка Q(1) располагается вблизи точки Р(2); Точка Q(N+1) располагается вблизи точки Р(3); Точка Q(2N+1) располагается вблизи точки Р(4); Точка Q(k) располагается вблизи точки Р(т-1);
158 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ #include<alloc.h> #include <process.h> main() { char lnfil[30], outfil[30]; intm-O, k. N,i,j.first-0; float *x, *y, *z, X, Y, Z, t, xdum, ydum, zdum, xA, xB, xC, xD, yA, yB, yC, yD, zA, zB, zC, zD. aO, a1. a2, аЗ, ЬО, M. b2, b3, cO, d, c2. c3; FILE *fpin. *fpout; printf /* "Input file: " */ ("Входной файл: "); scanf("%s", Infll); printf/* "Output file:"*/ ("Выходной файл: "); scanf("%s", outfll); printf /* "Enter N, the number of intervals between" */ ("Введите число N - количеству интервалов между\п"); printf /* "two successive given points:" */ ("двумя последовательными точками: "); scanf("%d", &N); fpin-fopen(infil, "r"); if(fpin--NULL) { printf /* "File not available" */ ("Файл недоступен"); exit(1); } while(fscanf(fpin, "%*d %f %f %f", &xdum, &ydum, &zdum)>0)m-H-; fclose(fpin); fpin - fopen(infil, "r"); x-(float *)malloc((m+1) * sizeof(float)); у-(float *)malloc((m+1) * sizeof(float)); z- (float *)malloc((m+1) * sizeof(float)); if (z-- NULL){printf /* "Not enough memory" */ ("He хватает памяти"); exit(1);} for(i-1;i<-m; i++) fscanf(fpin, "%*d %f %f %f", x+i, y+i, z+i); fclose(fpin); fpout-fopen(outfil, "w"); for(i-2;i<m-1;i++) { xA-x[i-1]; xB-x[i]; xC-x[i+1]; xD-x[i+2]; yA-y[i-1]; yB-y[i]; yC-y[i-»-1]; yD-y[i+2]; zA-z[i-1]; zB-z[i]; zC-z[i+1]; zD-z[i+2]; a3-(-xA+3*(xB-xC)+xD)/6.0; a2-(xA-2*xB+xC)/2.0; a1-(xC-xA)/2.0; aO-(xA+4*xB+xC)/6.0; b3-(-yA+3*(yB-yCKyD)/6.0; Ь2-(уА-2*уВ+уС)/2.0; Ы-{уС-уА)/2.0; Ь0-(уА+4*уВ+уС)/6.0;
3.8. КРИВЫЕ ТИПА В-СПЛАЙНА 159 c3-(-zA+3*(zB-zC)+zD)/6.0; c2-(zA-2*zB+zC)/2.0; d-(zC-zA)/2.0; cO-(zA+4*zB+zC)/6.0; for Q-f irst; j<-N; j++) { t-(float)j/(float)N; X-((a3*t+a2)*t+a1)*t+a0; Y-((b3*t+b2)*t+M)*t+b0; Z-((c3*t+c2)*t+d)*t+cO; fprintf(fpout, "%d %f %f %f\n'\ (i-2)*N+j+1, X, Y, Z); } first-1; } fprintf(fpout, "Faces- ГраниЛгГ); k^-(m-3)*N+1; forQ-1;j<k;j-H-)fprJntf(fpout,M%d%d.\nMlj,j+1); fclose(fpout); } Перед завершением этого параграфа следует упомянуть, что чрезвычайно легко получить замкнутую гладкую кривую. Кри- вая В-сплайна начинается вблизи второй точки (точка 2) и за- канчивается вблизи предпоследней точки (точка т - 1). Для замкнутой кривой эти две точки должны совпадать. Также долж- ны совпадать точка 1 с точкой т -ч2 и точка 3 с точкой т, что схематично можно изобразить как: 1 2 3 4 1 , m-2 m-1 m Так должны появиться два совпадающих отрезка прямой линии. Стоит также отметить, что могут совпадать и две или более последовательных точек. Это свойство используется в качестве средства для "стягивания" кривой к заданной точке. Пусть, на- пример, в случае незамкнутой кривой необходимо аппроксими- ровать и заданные концевые точки (точка 1 и точка т). Самой первой точке можно присвоить номера 1, 2, 3, что заставит кри- вую начинаться точно с этой точки. На первый взгляд это может показаться странным, поскольку в соответствии с выражениями (3.4) каждая вычисляемая точка (jc, у, z) определяется по четы- рем заданным точкам и можно подумать, что кривая будет начи- наться с точки 1, если она совпадает еще с тремя точками 2, 3 и 4. Но по (3.4) коэффициент для точки 4 равен F2(t), а из уравнений (3.5) следует, что при t = 0 этот коэффициент будет равен нулю и
160 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ это действительно имеет место для самой первой вычисляемой точки. Поэтому на позицию первой точки эффективно влияют только точки 1, 2 и 3 , а поскольку эти точки фактически являют- ся одной точкой, то кривая начинается именно с нее. 3.9. КАБЕЛИ После изучения материала параграфов 3.7 и 3.8, мы в состоя- нии разработать программу для формирования "кабелей" из па- раграфа 2.5. Для этой цели будут особенно полезны наши функ- ции на языке Си initrotate и rotate, описанные в параграфе 3.7. При наличии пространственной кривой, заданной в виде по- следовательности точек, предпочтительно полученных с по- мощью программы CURVE3 (см. параграф 3.8), эти точки будут использованы в качестве центров окружностей. С клавиатуры вводится радиус R окружности, а также число п — количество точек на каждой окружности (как обычно, каждая окружность будет аппроксимироваться правильным многоугольником с п вершинами). Вершины первого многоугольника будут вычис- ляться специальным образом, существенно отличающимся от способа получения остальных многоугольников. Обозначим пер- вые три заданные точки на кривой через С0, С{, С2. Точка Сх бу- дет центром первой окружности, точка С2 — второй и так далее, но не будет ни одной окружности, связанной с самой первой точ- кой, как центром окружности. Начнем с использования точек С0 и С2 для получения оси первой окружности (с центром в точке Cj). Уравнение плоскос- ти, в которой лежит эта окружность, может быть выражено через координаты jcj, у., zi этих трех точек С4 (/ = 0, 1,2) : ах + by + cz = d где а = х2 - х0 ь=у2-у0 dssaxl + by{ +cz{ Поскольку n = [a, b, с] это вектор, начинающийся в точке CQ и заканчивающийся в точке С2, то прямая линия, проходящая через точку С, и имеющая то же направление, как п, является
3.9. КАБЕЛИ 161 осью интересующей нас окружности. Можно найти некоторый вектор г = [г , г , г ], перпендикулярный этой оси: Гх = *' ry= ~а' rz = ^ если 'а' - ^ / или ' ЬI >6 гг = 0, г = с, г = -6 если I a I < (3 и I b I < (5 где 5 — некоторое малое положительное число, скажем 10 . Вектор г был выбран таким образом, что его скалярное произве- дение с вектором п равно нулю; логическое условие предотвра- щает получение вектора г с длиной, почти равной 0. После вычисления значения параметра можно будет использовать точку со следующими координатами в качестве первой точки на искомой окружности: x[0] = xt + Rrx/ L ylO]-yt + Rr IL z[0] = Zi + Rrz/ L Поскольку на каждой окружности нам надо найти п точек, то будем использовать соотношение в = 2я/п для вычисления остальных п - 1 точек на окружности, поворачи- вая каждый раз предыдущую точку вокруг оси окружности на угол в. На первый взгляд все это сложно, но на самом деле нужно всего лишь вызвать наши функции initrotate и rotate. Сначала обратимся к функции initrotate с аргументами в следующем порядке xv yv Zj, а, й, с, 0, затем выполним цикл for: for(M;Kn;l++) rotate(x[l-1], y[l-1], z[M]. x+i, y+i, z+l); (Напомним, что в языке Си выражение x + i обозначает адрес элемента массива *[/], его вполне можно применять вместо вы- ражения &х [i ]). Теперь нужно вычислить остальные окружности и это можно сделать путем определения п точек на каждой новой окружности по таким же точкам, как на предшествующей окружности. Обоз- начим буквой А центр этой предшествующей окружности (будем ее кратко называть окружность А) и буквой В — новую окруж- ность — окружность В, То есть сразу же после формирования са- мой первой окружности имеем А = С{ и В = С2. Обозначим ось 6—273
162 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ окружности А через /. Если точка В лежит на прямой /, то п точек окружности В могут быть получены путем простого переноса со- ответствующих точек на окружности А: все, что для этого нужно сделать, это добавить хБ - дсА, ув - уА и zB - zA к соответствующим координатам п точек на окружности А. Не так все просто, если точка В не лежит на прямой /. В этом случае окружность В может быть получена путем поворота ок- ружности А, или, более точно, путем поворота п точек окружно- сти А так, чтобы их новые позиции совпадали бы с желаемыми точками окружности В. Снова можно использовать функции initrotate и rotate при условии, что перед этим можно определить ось и угол поворота. Заметим, что этот поворот существенно отличается от поворота, применявшегося для поиска п - 1 точек на первой окружности. Рассмотрим теперь три плоскости, пока- занные на рис. 3.17: а - плоскость, в которой лежит окружность А; /3 - плоскость, проходящую через прямую / и точку В; у - плоскость, проходящую через точку М, лежащую посредине отрезка АВ и перпендикулярную этому отрезку. Рис. 3.17. Поворот окружности А для получения окружности В
3.9. КАБЕЛИ 163 Для плоскости а имеем уравнение ах + by + cz = d Значения коэффициентов я, b, end были вычислены для первой окружности (с центром в точке Cj). Несколько позже покажем, как найти аналогичные значения для других окружностей. Для плоскости/J будем использовать параметрическое пред- ставление вместо уравнения. Известно, что эта плоскость прохо- дит через точку А и что она параллельна векторам [а, Ь, с] и АВ. Это приводит к следующему описанию плоскости /J, в котором буквами Я и р обозначены параметры: lxyz]-[xAyAzA]+X[abc]+/ilxB-xA ув-уА zB-zA] Плоскость у перпендикулярна вектору АВ, поэтому ^ = *В-*А <*у = Ув-Уа < = ZB"ZA можно использовать в качестве коэффициентов ее уравнения. Плоскость проходит через точку М, лежащую посредине отрезка АВ. Следовательно, при: *м = (*А + *в)/2 Умш<Уа + Ув)П ZM=(zA + ZB)/2 do = dxxM + dyyu + dzz4 найдем уравнение для плоскости у: dxx + dyy + dzzssdo Плоскости а, /?, у имеют одну общую точку пересечения, ко- торую обозначим через Р, ее можно вычислить. Тогда прямая линия т, проходящая через точку Р и перпендикулярная плос- кости/? будет нужной нам осью поворота, угол АВР (то есть угол между прямыми линиями АВ и ВР) будет желаемым углом пово- рота. Обозначим этот угол буквой ^>. В действительности нам нужна направленная ось, другими словами, кроме точки Р нам нужно знать вектор v, который расположен так, что если бы мы рассматривали его лежащим на прямой линии га, то нам бы при- шлось поворачивать окружность А на угол <р вокруг вектора v в положительном направлении (соответствующем направлению вектора v подобно перемещению нормального винта). 6**
164 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ Этот вектор v находится как векторное произведение векто- ров РА и РВ: v = РА х РВ Вектора РА и РВ можно также использовать для нахождения угла <р по их скалярному произведению РА • РВ = РА х РВ х cos <p где через РА и РВ обозначены длины векторов РА и РВ. После поворота окружности А для получения окружности В нужно не забыть сформировать еще новый вектор [а Ъ с], пер- пендикулярный окружности В, поскольку на следующем шаге окружность В будет выступать как старая окружность А и тогда нам потребуются новые значения для а, Ьу с. Хотя очень часто удобно разместить начало вектора в любой точке, в данном слу- чае поместим его в начало О системы координат, тогда будем ис- кать новую точку (а\ Ъ\ с'), получаемую путем поворота точки (a, by с) вокруг вектора v, который в данный момент также начи- нается в точке О. Таким образом, новые значения программных переменных а, ft, с находятся следующим путем: initrotate(0.0,0.0,0.0. v1, v2, v3, phi); rotate(a. b, c, &a, &b, &c); Остальные подробности по вычислению значений параметров Р, v, <р можно найти в программе CABLE. /* CABLE: Эта программа считывает файл данных для пространственной кривой и формирует новый файл с описанием кабеля. Круговые сечения кабеля представляют собой многоуголь- ники с п вершинами, которые аппроксимируют окружности с радиусом R. Оба числа п и R вводятся с клавиатуры. Программа должна быть скомпонована совместно с модулем TRAFO, в котором определены функции 'Initrotate' и 'rotate'. */ #include<stdio.h> #include<math.h> #include<alloc.h> #lnclude <process.h> void initrotate(doublea1, double a2, double a3, double v1, double v2, double v3, double alpha); void rotate(double x, double y, double z, double *px1, double *py1, double *pz1);
3.9. КАБЕЛИ 165 intzero(doublex); double *getdouble(int n); double *enlarge(double *px, Int N); void ermes(char *s); main() { charinfil[30],outfil[30]; Int i, n, j, m, jn, JnO, k, tablesize; double R, *x, *y, *z. xCO, yCO, zCO, xC1.yC1.zC1, xC2. yC2, zC2. *xC, *yC, *zC, a, b, c. d, rx, ry, rz. pi, theta, Len, *getdouble(), *enlarge(), xA, yA, zA, xB, yB, zB, dx, dy, dz, dO, d, c2, cO, xM, yM, zM, e1, e2, eO, denom, lambda, mu, xP, yP, zP, xAP, yAP, zAP, xBP, yBP, zBP, v1, v2, v3, cosphi, phi; FILE *fpin, *fpout; prlntf("Входной файл: "); scanf("%s", infil); /* Input file */ fpin-fopen(infil, "r"); if (fpln - - NULL) егтеэСФайл не доступен"); /* File not available */ if ( fscanf(fpin, "%*d %lf %lf %1Г. &xC0, &yC0. &zC0)!— 3 I I fscanf(fpln, M%*d %lf %lf %1Г. &xC1, &yC1, &zC1) !-3 I I fscanf(fpln, "%*d %lf %lf %1Г. &xC2, &yC2, &zC2) !-3 ) ermes(nВходной файл не верен"); /* Input file incorrect */ printf("Выходной файл: "); scanfC%s", outfil);/* Output file */ fpout-fopen(outfil, "wM); prlntf("Сколько точек на каждой окружности? м); /* How many points on each circle? */ scanfr%d", &n): printfCРадиус:"); scanf("%lf\ &R);/* Radius */ a-xC2-xC0; b-yC2-yC0; c-zC2-zC0; d-a*xC1+b*yC1+c*zC1; /* Первая окружность имеет центр в точке (хС 1, уС 1, zC 1), радиус R и лежит в плоскости ах + by + cz - d */ х - getdouble(n); у - getdouble(n); z - getdouble(n); if (zero(a) && zero(b)){rx-0; ry-c; rz^b;} else{rx-b; ry—a; rz-O;} Len-sq rt(rx* rx+ry * ry+rz * rz); rx/-Len; ry/-Len; rz/-Len; /* (rx, ry, rz) is a unit vector perpendicular to (a, b, c) (rx. ry, rz)--единичный вектор, перпендикулярный (a, b, с) */ x[0]-xC1+rx*R; y[0]-yC1+ry*R; z[0]-zC1+rz*R; pi-4.0*atan(1.0); theta-2*pi/n; /* Computation of n points on the first circle: */ /* Вычисление п точек на первой окружности */ initrotate(xC1, yC1, zC1, a, b, с, theta);
166 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ for(i-1;i<n; i++) rotate(x[i-1], y[i-1], z[i-1], x+i, y+i, z+i); /* Count number of circles (number of points minus 1 read from input file): */ /* Счетчик числа окружностей (число точек во входном файле минус 1) */ т-2; while ( fscanf(fpin. "%*d %lf %lf %1Г. &xC0, &yC0, &zC0)--3) m++; tablesize-m*n; x - enlarge(x, tablesize); у - enlarge(y, tablesize); z - enlarge(z, tablesize); fclose(fpin); fpin-fopen(infil, V); /* Перемотка */ /* Rewind */ fscanf(fpin, "%*d %lf %lf %lf\ &xC0, &yC0, &zC0); /* Пропуск */ /* Skip */ xC-getdouble(m); yC-getdouble(m); zC-getdouble(m); forO-0;j<m;j-H-) fscanf(fpin, "%*d %lf %lf %1Г, xC+j, yC+j, zC+j); /* Теперь точка (хС[0]. yC[0], zC[0]) будет центром заданной окружности с радиусом R и лежащей в плоскости ах + by + cz -d. Необходимые п точек на этой окружности уже были вычислены; их координаты записаны в элементах х[0]. у[0]. z[0] х[п-1], у[п-1]. z[n-1]. Остальные т-1 окружностей будут получены из этой первой окружности с помощью поворотов */ /* (хС[0]. уС[0], zC[0]) is now the center of the given circle, lying In plane ax + by + cz -d, and with radius R. The n relevant points on this circle have already been computed; their coordinates are x[0]. y[0], z[0] x[n-1], y[n-1]. z[n-1]. The other m-1 circles will be derived from this first one by means of rotations. */ fclose(fpin); for(]-1;j<m;j++) { jn-j*n; jnOjn-n; xA-xC[j-1]; yA-yCD-1]; zA-zC[j-1]; xB-xCD]; yB-yCD]: zB-zCD]; dx-xB-xA; dy-yB-yA; dz-zB-zA; c1-a*a+b*b+c*c; c2-a*dx+b*dy+c*dz; cOd-a*xA-b*yA-c*zA; xM-0.5*(xA+xB); yM-0.5*(yA+yB); zM-0.5*(zA+zB);
КАБЕЛИ dO-dx*xM+dy*yM+dz*zM; e1-dx*a+dy*b+dz*c; e2-dx*dx+dy*dy+dz*dz; eO-dO-dx*xA-dy*yA-dz*zA; denom-c1*e2-c2*e1; If (fabs(denom)< 1e-12) { /* Направление не изменилось. Вместо бесконечно удаленной точки Р будем применять простой перенос. for(K);i<n;i++) { x[jn+l]-x[jnf>l] + dx; yDn+l]-y[Jn(W] + dy; zfjn+i]-z[jn0+l] + dz; } } else /* Направление изменилось. Полигон будет повернут на угол phi вокруг вектора v, проходящего через точку Р. { Iambda-(c0*e2-c2*e0)/denom; mu-(c1*ef>-c0*e1)/denom; xP-xA+lambda*a+mu*dx; yP-yA+lambda*b+mu*dy; zP-zA+lambda*c+mu*dz; /* Точка Р (пересечение трех плоскостей) - центр поворота. хАР-хА-хР; уАР-уА-уР; zAP-zA-zP; хВР-хВ-хР; уВР-уВ-уР; zBP-zB-zP; v1-yAP*zBP-yBP*zAP; v2-xBP*zAP-xAP*zBP; v3-xAP*yBP-xBP*yAP; /* (v1, v2, v3) - направление оси вращения */ cosphl-{xAP*xBP+yAP*yBP+zAP*zBP)/ sqrt((xAP*xAP+yAP*yAP+zAP*zAP)* (xBP*xBP+yBP*yBP+zBP*zBP)); phl-(cosphl--0?0.5*pl: atan(sqrt(1.0-cosphl*cosphl)/cosphl)); /* phi-угол поворота */ lnltrotate(xP, yP, zP, v1, v2, v3. phi); for(l-0;Kn;l++) rotate(x[jnOI], y[jnO+i], z[JnOI], x+Jn+l,y+Jn+l,z+jn+l); lnltrotate(0.0,0.0,0.0, v1, v2, v3, phl); rotate(a, b, с, &а, &b, &c); } d-a*xC[J}fb*yC[j}fc*zCU]; } If (fpout - - NULL) ermes("Проблема с выходным файлов"); /* Problem with output file */
168 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ for(k-0;k<m*n; к-и-) fprintf(fpout, "%d %f %f %f\n", k+1. x[k], y[k], z[k]); fpnntf(fpout, "Faces- ГраниЛп"); for (k-n-1; k>0; k- -) fprintf(fpout,u %d". k+1); fprintf(fpout, "An"); for(k-(m-1)*n; k<m*n; к-и-) fprintf(fpout," %сГ, k+1); fprlntf(fpout, "An\n"); for(j-1;j<m; j++) { jn-j*n+1; jnO-jn-n; for(i-0;i<n-1;i++) { fprintf(fpout." %d %d %d.\n", jn+i+l.-QnO+i), jnO+i+1); fprintf(fpout," %d %d %d.\n", jnO+i,-<jn+i+1), jn+i); } fprintf(fpout, " %d %d %d.\n\n", jn,-0nO+n-1), jnO); fprlntf(fpout," %d %d %d.\n\n'\ jn0+n-1,-jn, jn+n-1); } fclose(fpout); } Intzero(doublex) { return f a bs(x) < 1e-5; } double *getdouble(int n) { double *p; p-(double *)malloc(n * sizeof(double)); If (p - - NULL) ermes("He хватает памяти"); /* Not enough memory */ return p; } double *enlarge(double *px, Int N) { px - (double *)realloc(px, N*slzeof(double)); if (px - - NULL) ermes("He хватает памяти"); /* Not enough memory */ return px; } void ermes(char *s) { prlntf(s); exlt(1); } За пояснениями по демонстрации работы программы обра- щайтесь к параграфу 2.5 и рис. 2.16, 2.19 и 2.20. З.Ю.ПОВЕРХНОСТИ ТИПА В-СШ1АЙНА В параграфе 3.8 были рассмотрены пространственные кри- вые, точки которых имели координаты jc(0, y(t)> z(0, где t яв- ляется параметром. Рассмотрим теперь поверхности, точки ко- торых имеют координаты х(и, v), у(и, v), z(w, v), где и и v — два
3.10. ПОВЕРХНОСТИ ТИПА В-СПЛАЙНА 169 независимых параметра. Снова будем использовать аппроксима- цию с помощью В-сплайна. Вместо последовательности т задан- ных точек теперь будем задавать набор из пт точек, которые бу- дут образовывать таблицу в следующем виде: рп Р21 Р«1 Р12 • Р22 Рп2 • Р,т 1т Р2т Р пт Будем применять названия горизонтальные и вертикальные ряды, которые относятся к строкам и столбцам этой таблицы. Например, можно сказать, что точка Р1т является самой правой точкой в первом горизонтальном ряду. Фактически программа будет считывать нормальный объект- ный файл в формате программы D3D, в котором точки задаются в порядке последовательности Рп, Р12, ..., Pwm. Программа получает значения переменных типе клавиатуры и ожидает, что файл содержит точки с номерами от 1 до пт. То есть точка Р.. в приведенной выше таблице во входном файле имеет номер Jfc-O'-Dm+y Таблицу точек Р.. можно также интерпретировать как два на- бора кривых: для каждой "горизонтальной" кривой параметр / является константой, а параметр j изменяется от 1 до /я, а для каждой "вертикальной" кривой параметр/ — константа, а пара- метр i изменяется от 1 до п. Как уже знаем, в процессе аппрокси- мации В-сплайном самая первая и самая последняя точки, хотя и участвуют в процессе вычислений, но не аппроксимируются. То же самое относится и к точкам Р.. в вышеприведенной таблице для которых i = 1 или i = п, а также для тех точек, для которых /= 1 или/=т. Для вычисления точек сетки на "четырехугольнике" поверхности нам теперь потребуются по четыре точки в каждом направлении, что вместе дает следующие шестнадцать точек: рм,м Vi р/+1,/-1 Р/+2,/-1 РМ,; Р.-.у * Р»1.У Р/+2,у Р Vi Р/+1,/И Р/+2,/Н РЫ,Д2 PiV*2 Р*+1,/+2 P/+2,/f2
170 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ (3.6) Все эти точки будут использованы для аппроксимации "четы- рехугольника" в середине (с точкой Р.. в верхнем левом углу и с точкой Р.+1 ^1 в нижнем правом углу). Этот четырехугольник в середине будет поделен на N х М малых элемента поверхности (то есть мы можем пересчитать N элементов по вертикальному и А/ элементов по горизонтальному направлениям). Значения NnM наша программа получит с клавиатуры. Формулы для четырех функций F_x, F0, Fx, F2> определенные в (3.5) (см. параграф 3.8), подставим в формулы (3.4) в их двух- мерном представлении. Поскольку эти формулы имеют идентич- ную структуру для всех трех координат х, у, z, то ниже приведем только одну, для координаты х: х- F^uiF^Mx^^ +F-{(u)F0(v)xM.} + F_{ (u)F{ MxhlJH + F_x (u)F2(v)xhlj+2 + F0(u)F_x MXij_{ + V">Vv4,y + FQ(u)F{ (v)xiJ+l + FQ(u)F2MxiJ¥2 + Fliu)F_lMxM^l +FlMF0MxMJ + Fx (u)F{ MxMJH + Fx (u)F2MxMJ¥2 + F2(u)F_{ <v)*MJ4 + F2(u) F0MxMJ + F2iu)FlMxMJH +F2{u)F2iv)xi+2^2 - Напомним, что в параграфе 3.8 мы фактически применяли не сами формулы (3.4^, а их эквивалентные формы (3.2) таким об- разом, что имели суммы шестнадцати членов, в которых содер- жались следующие произведения: 33323 323222 2 3 2 32 * и v , и v , и v, и , и v , и v , и v, и , uv , uv , uvy и, v , v , v,..l Коэффициенты при этих членах определялись по сравни- тельно сложным формулам, которые здесь опускаются во избе- жание дублирования, но они применяются в программе BSPLSURF и их м*ожно там найти. /*BSPLSURF Поверхности В-сплайна Эта программа должна получить объектный файл в формате программы D3D, в котором определена сетка точек. Программа запрашивает пользователя ввести значения тип и файл должен содержать точно тп точек.
3.10. ПОВЕРХНОСТИ ТИПА В-СПЛАЙНА 171 Предполагается, что эти точки образуют сетку следующим образом: 1 т+1 2т+1 2 т+2 2т+2 . * т .. 2т .. Зт (п-1)т+1 (п-1)+2 ... тп Ни одно из значений т или п не должно быть меньше 4. Положение точек в трехмерном пространстве произвольное, совсем не обязательно, что какие-либо две точки не могут иметь совпадающие координаты х, у и z. Если объектный файл имеет секцию, начинающуюся со слова "Faces - Грани", то эта секция игнорируется. Дополнительно должны быть введены два числа М и N, они определяют количество интервалов, используемых в процессе аппроксимации поверхности: будет определено М интервалов между точками 1 и 2 и N интервалов между точками 1 и т+1 и так далее. Кривая поверхность типа В-сплайна, являющаяся аппроксимацией заданных точек, записывается в выходной файл в формате программы D3D. */ ♦include <stdio.h> ♦include <process.h> ♦Include <alloc.h> main() { FILE*fp1,*fp2; charstr[30]; float *xx, *yy, *zz, x, y, z. v, u, u2, u3, a00,a01,a02,a03, a10,a11,a12,a13, a20,a21,a22.a23, а30,а31.а32, аЗЗ, Ь00,Ь01,Ь02, ЬОЗ, М0,М1,Ы2,ЫЗ, Ь20,Ь21,Ь22, Ь23, Ь30,Ь31,Ь32, ЬЗЗ, с00,с01,с02, сОЗ, С10,с11,с12,с13, С20,с21,с22,с23. с30.с31,с32, сЗЗ, х00,х01,х02, хОЗ, х10,х11,х12,х13, х20,х21,х22,х23, х30,х31.х32,хЗЗ,
172 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ у00,у01,у02, уОЗ, у10,у11,у12,у13, у20,у21,у22, у23, у30,у31,у32,уЗЗ, z00,z01,z02,z03, z10,z11.z12,z13, z20,z21,z22,z23, Z30,z31,z32,z33; int size, i, J, m, n, M, N, mn, I, k, I, J, A, B,C, ii.jj.nn, mm, P, h00,h01,h02,h03, h10.h11,h12,h13, h20,h21,h22, h23. h30,h31,h32,h33; do { prlntf/* "Enter m and n (both at least 4):" */ ("Введите числа тип (причем оба не менее 4):"); scanf("%d %d", &m, &n); } while(m<4 11 n<4); printf/* "Input file:"*/ ("Входной файл: "); scanf("%s", str); fp1-fopen(str, "г"); prlntf/* "Output file:"*/ ("Выходной файл:"); scanf("%s", str); fp2-fopen(str, "w"); if(fp1--NULLIIfp2--NULL) { prlntf /* "File problem" */ ("Проблема с файлом"); exit(1); } mn - m * n; size -(mn+1) * sizeof(float); xx - malloc(size); yy - malloc(size); zz - malloc(size); for(l-0;Kmn;l++) { if (fscanf(fp1, "%d %f %f %Г, &k, &x, &y, &z)l-4) { printf/* "Too few points in file" */ ("Слишком мало точек в файле"); exit(1); } if(k<1 II k>mn) { prlntf /* "Point number incorrect" */ ("Количество точек (%d) неверное",k); exit(1); xx[k] - x; yy[k] - y; zz[k] - z; fclose(fpl); printf /* "Enter M and N:" */
3.10. ПОВЕРХНОСТИ ТИПА В-СПЛАЙНА 173 ("Введите числа М и N:"); scanf("%d %d", &M, &N); /* Предположим, что имеем п рядов из m точек, что дает (п-1) * (т-1) прямоугольников. Среди них только (п-3) * (т-3) прямоугольников будут аппроксимированы. Считаем, что каждый из этих прямоугольников должен быть поделен на М * N малых прямоугольников, которые будем называть 'элементами1. Для каждой горизонтальной линии сетки в выходном файле будет определено А точек */ А-(т-3)*М+1; printtf ! j\nM); for(i-2;K-n-2;l++) for(|-2;j<-m-2;j++) { h11-(l-1)*m + J; h10-h11-1;h12-h1O2;h13»h10+3; hOO-MO-m; h01 -hOOM; h02«h(XH2; h03-hOO+3; h20-h1f>m; h21 -h20H; h22-h20f2; h23-h20+3; h30-h2f>m; h31 -h30M; h32- h3C>2; h33-h30+3; printf("%3d %3d\n",i, j); /* Show that we are busy */ /* Указание на занятость */ /* Здесь 16 точек пронумерованы следующим образом: POO P01 Р02 РОЗ Р10 Р11 Р12 Р13 Р20 Р21 Р22 Р23 РЗО Р31 Р32 РЗЗ На данном шаге будет аппроксимироваться внутренняя область между точками Р11, Р12, Р21, Р22(для текущих значений! и J) V хОО - xx[h00]; уОО - yy[h00]; zOO - zz[hOO]; х01 - xx[h01]; y01 - yy[h01]; z01 - zz[h01]; x02 - xx[h02]; y02 - yy[h02]; z02 - zz[h02]; x03 - xx[h03]; y03 - yy[h03]; z03 - zz[h03]; x10-xx[h10];y10-yy[h10];z10-zz[h10]; x11-xx[h11];y11-yy[h11];z11-zz[h11]; x12-xx[h12];y12-yy[h12];z12-zz[h12]; x13-xx[h13];y13-yy[h13];z13-zz[h13]; x20 - xx[h20]; y20 - yy[h20]; z20 - zz[h20]; X21-xx[h21];y21-yy[h21];z21-zz[h21]; x22 - xx[h22]; y22 - yy[h22]; z22 - zz[h22]; x23 - xx[h23]; y23 - yy[h23]; z23 - zz[h23]; x30 - xx[h30]; y30 - yy[h30]; z30 - zz[h30]; X31-xx[h31];y31-yy[h31];z31-zz[h31]; x32 - xx[h32]; y32 - yy[h32]; z32 - zz[h32]; x33 - xx[h33]; y33 - yy[h33]; z33 - zz[h33];
174 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ аЗЗ - (хОО-хОЗ-хЗ(НхЗЗ +3*(-х01+х02-х 10х 13+х20-х23+х31 -х32) +9*(х11-х12-х21+х22))/36; а32 - (-хОО-х02+хЗСН-х32 + 2*(х01-х31) + 3*(х1(Нх12-х20-х22) + 6*(-х1 1+х21))/12; а31 -(хОО-х02-хЗ(Нх32 + 3*(-х1(Нх12+х20-х22))/12; аЗО - (-хОО-х02+хЗО*х32 + 3*(х10+х12-х20-х22) + 4*(-х01+х31) + 12*(х11-х21))/36; а23 - (-хОО* x03-x20f x23 + 2*(х1(Ьх 13) + 3*(х01-х02+х21-х22) + 6*(х12-х11))/12; а22 -(xOQfx02+x2(Kx22 + 2*(-х01-х10-х12-х21))/4 + х11; а21«(х02-х0О-х2(Нх22 + 2*(х1(>-х12))/4; а20 - (х(ХНх02+х2ОЪс22 - 2*(х10+х12) + 4*(х01+х21)-8*х11)/12; a13-(x00-x03-x2Ofx23 + 3*(x02-x01+x21-x22))/12; а 12 -(-x00-x02+x20fx22 + 2*(х01-х21))/4; а11 - (хОО-х02-х2(Нх22)/4; аЮ - (-х00-х02+х2(Нх22 + 4*(-хОНх21))/12; аОЗ - (хОЗ-хОО-х2(Н-х23 + 3*(х01-х02+х21-х22) + 4*(х13-х10) + 12*(х11-х12))/36; а02 - (х(ХНх02+х2(Нх22 - 2*(х01+х21) + 4*(х1(Нх12)-8*х11)/12; а01 -(х02-х00-х20+х22 + 4*(х12-х10))/12; аОО - (х(ХНх02+х2(Н-х22 + 4*(х01 +х 10+х 12+х21) + 16*х11)/36; ЬЗЗ-(у00-у03-уЗО*уЗЗ +3*(-у0 Ну02-у 1 (Ну 13+у20-у23+у31-у32) +9*(у11-у12-у21+у22))/36; Ь32 - (-y00-y02+y3(>fy32 + 2*(у01-у31) + 3*(у 10+у 12-у20-у22) + 6*(-у 11+у21))/12; Ь31 - (у00-у02-у30+у32 + 3*(-у 10+у 12+у2(Ьу22))/12; ЬЗО - (-у(ХЪу02+уЗ(Ну32 + 3*(у 10+у 12-у2(Ьу22) + 4*(-у01+у31) + 12*(у11-у21))/36; Ь23 - (-уОО+уОЗ-у2(Ну23 + 2*(у 10-у 13) + 3*(у01-у02+у21-у22) + 6*(у 12-у 11))/12; Ь22 - (yOOfy02+y2CH-y22 + 2*(-у01-у 10-у 12-у21))/4 + у 11;
3.10. ПОВЕРХНОСТИ ТИПА В-СПЛАЙНА 175 Ь21 - (y02-y(Xby2(>fy22 + 2*(у10-у 12))/4; Ь20 - (y(XHy02+y20f y22 - 2*(у 1(Ну 12) + 4*(у01+у21)-8*у11)/12; МЗ - (у00-у03-у2О+у23 + 3*(у02-у01+у2 Ьу22))/12; Ы2 - (-уОО-у02+у20*у22 + 2*(у01-у21))/4; Ы1 - (у00-у02-у2О+у22)/4; ЬЮ- (-у(ХЬу02+у2СНу22 + 4*(-у01+у21))/12; ЬОЗ - (уОЗ-уОО-у2(Ну23 + 3*(у01-у02+у21-у22) + 4*(у13-у10)+ 12*(у11-у12))/36; Ь02 - (уО(Н-у02+у2(Ну22 - 2*(у01+у21) + 4*(у10+у12)-8*у11)/12; Ь01 -(у02-уОО-у20+у22 + 4*(у12-уЮ))/12; ЬОО - (уОО+у02+у2(Ну22 + 4*(у01+у 10*у12+у21) + 16*у11)/36; c33-(z(Xbz03-z30+z33 +3*(-z01+z02-z10+z13+z20-z23+z31-z32) +9*(z11-z12-z21+z22))/36; c32 - (-z0O-z02+z3Ofz32 + 2*(z01-z31) + 3*(z1(Hz12-z2(bz22) + 6*(-z11+z21))/12; c31 - (zOO-z02-z3(Hz32 + 3*(-z1(Hz12+z20-z22))/12; c30-(-z(X>-z02+z3(Hz32 + 3*(z1(Hz12-z20-z22) + 4*(-z01+z31) + 12*(z11-z21))/36; c23- (-zOOfz03-z20fz23 + 2*(z10-z13) + 3*(z01-z02+z21-z22) + 6*(z12-z11))/12; c22 - (z(XHz02+z2(Hz22 + 2*(-z01-z10-z12-z21))/4 + z11; c21 -(z02-z00-z2(>fz22 + 2*(z1Cbz12))/4; c20 - (zOOfz02+z20fz22 - 2*(z1(Hz12) + 4*(z01+z21)-8*z11)/12; с 13 - (z00-z03-z20fz23+3*(z02-z01+z21-z22))/12; c12 - (-z0O-z02+z2O4-z22 + 2*(z01-z21))/4; d 1 - (z00-z02-z2Ofz22)/4; с 10 - (-z00-z02+z20*z22 + 4*(-z01+z21))/12; c03 - (z03-zOO-z2(H-z23 + 3*(z01-z02+z21-z22) + 4*(z13-z10) + 12*(z11-z12))/36; c02 - (z(X>fz02+z2(>fz22 - 2*(z01+z21)
176 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ + 4*(z1f>z12)-8*z11)/12; с01 - (z02-zOO-z2(Hz22 + 4*(z12-z10))/12; сОО - (zOO+z02+z2(H-z22 + 4*(z01+z1(Hz12+z21) + 16*z11)/36; /* В выходном файле будем использовать номера точек к, которые должны быть вычислены по управляемым переменным i, j. I, J. Каждый прямоугольник, определяемый парой (i.j), должен быть поделен на N*M элементов, для чего используются счетчи '.и I (до значения N) и J (до значения М). Чтобы избежать дублирования точек, значения I и J нормально начинаются с 1 за исключением самых младших значений i и j (равных 2х для которых начальные значения I и J устанавливаются соответственно равными 0. (Один из всех прямоугольников, разбиваемых на элементы, при I-J-2 является самым левым верхним прямоугольником). Если I-0 (что предполагает i-2), то к - (|-2)М + J + 1, в противном случае к - А + (l-2)N А + (I—1)А + (j-2)M + J + 1. Это означает, что в любом случае будем иметь: k-{(i-2)N + l}A + {(j-2)M + 1} +J (Значение А определяет общее число точек сетки в горизонтальном ряду) */ В-(|-2)*М+1; for (I -(I - -2 ? 0: 1); I <- N; I++-) { u -(float)l/N; u2 - u*u; u3- u2*u; C-((i-2)*N+l)*A+B; for (J -0 - -2 ? 0 : 1); J <- M; J+f) { k-C + J; v-(float)J/.VI; x - ((a03*v+a02)*v+a01)*v+a00+ u*(((a13*v+a12)*v+a11)*v+a10}f u2*(((a23* v+a22)*v+a21)* v+a20>+- u3*(((a33*v+a32)*v+a31)*v+a30); у - ((b03*v+b02)*v+b01)*v+b0f> u*(((b13*v+b12)*v+b11)*v+b10>f u2*(((b23*v+b22)*v+b21)*v+b20>+ U3*(((b33*v+b32)*v+b31)*v+b30); z - ((c03*v+c02)*v+c01)*v+c0f> U*(((c13*v+c12)*v+c11)*v+c10>f u2*(((c23* v+c22)*v+c21)*v+c20)+ U3*(((c33*v+c32)*v+c31)*v+c30); fprintf(fp2, "%d %f %f %f\n", k, x, y, z); } } }
J. 70. ПОВЕРХНОСТИ ТИПА В-СПЛАЙНА \и fprintf(fp2, "Faces-ГраниЛгГ); nn-(n-3)*N; mm-A-1; /* mm-(m-3)* M */ for(ii-0;il<nn;ii++) for()j-0;jj<mm;]j++) { P-ii*A + jj + 1; /* Каждый почти прямоугольный элемент поверхности мы должны разделить на два треугольника, чтобы обеспечить расположение точек, определяющих вершины полигона, точно в одной плоскости. Знак минус в последовательности, например, Р -Q R, предотвратит вычерчивание отрезка прямой PQ в программе D3D. Поскольку любая грань каждого треугольника может быть видима с любой стороны, ее вершины будем описывать в порядке обхода как против часовой стрелки, так и по часовой стрелке */ fprlntf(fp2, "%d %d %d.\rT, P,-(P+A+1), P+1); fprintf(fp2, "%d %d %d.\n", P+A+1.-P, P+A); fprlntf(fp2, "%d %d %d.\n", P,-{P+A+1), P+A); fprintf(fp2, "%d %d %d.\n", P+A+1,-P, P+1); } fclose(fp2); } Рис. 3.19 был получен с помощью программы D3D на основе файла, созданного программой BSPLSURF. Входные данные для этой последней программы содержат 42 точки, некоторые из которых совпадают. Идея присвоения более одного номера для одной и той же точки кривой уже обсуждалась в конце параграфа 3.8 и эта идея вполне применима также и для поверхностей. Рассмотрим следующую таблицу при п = 6 и т = 7: 12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 Каждая строка в этой таблице соответствует ломаной линии на рис. 3.18. Однако, первая строка обозначает те же точки, что и вторая, а точки пятой строки совпадают с точками шестой стро- ки. Кроме того, первая точка в каждой строке совпадает со вто- рой точкой, а шестая совпадает с седьмой. При совпадающих точках программа D3D отображает только старший номер.
178 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ 11 18 АЛ I ' I I I I i Рис. 3.18. Отображение входного файла для программы BSPLSURF § Рис. 3.19. Результат аппроксимации поверхности с помощью В-сплайна Например, на рис. 3.18 точка с номером 9 обозначает также точ- ки 1, 2, 8. (Заметим, что реализация этой идеи для совпадающих точек несомненно имеет очень существенное значение, хотя можно подумать, что это лишь ненужное усложнение и можно задавать только несовпадающие точки — следует вспомнить, что первая и последняя точки не будут аппроксимированы). Номера точек 16 и 17 также исчезли на рис. 3.18, поскольку эти точки совпадают с точками 23 и 24, но совершенно по иной причине. Рис. 3.18 был построен с использованием команды Transform С Преобразование"). Ломаная линия, обозначенная номерами
3.11. ПЕРЕСЕЧЕНИЕ ЦИЛИНДРОВ 179 23, 24, 25, 26, 28 была получена из ломаной линии, которой при- надлежит точка 18 путем ее поворота на 90° вокруг оси (24-23), что означает совпадение точек 15, 16, 17 с точками 22, 23, 24. При задании N = М = 3 был в результате получен файл из 130 точек, который изображен программой D3D в виде рис. 3.19. Только что упомянутый максимальный номер 130 дает пре- красную возможность проверить соотношение между значения- ми параметров n, /n, N, M> i, ус одной стороны и наибольшим аб- солютным значением количества точек к> записанных в выход- ной файл с другой стороны. В общем случае имеем *={«-2)ЛГ + /}Л + <у-2)М+1+/ где величина Л=(т-3)М+1 обозначает количество точек в выходном файле, лежащих на "горизонтальной" линии. В примере рис. 3.19 имеем m = 7, n = 6, M*3, N = 3 Л-(7-3)хЗ + 1-13 Поскольку параметры i и/ используются в цикле for, где их мак- симальные значения равны л - 2 и m - 2 соответственно, то NnM будут максимальными значениями для I и J соответственно, а для нахождения максимального значения к используем значе- ния/ = 4, у = 5, /=3,/ = 3, что дает к={(4-2)хЗ + 3}х13 + (5-2)хЗ + 1+3=130 З.П.ПЕРЕСЕЧЕНИЕ ЦИЛИНДРОВ На рис. 1.13 в параграфе 1.5 было показано несколько изобра- жений объекта, состоящего из двух пересекающихся цилиндров, часто называемого Т-образным соединением. Теперь рассмотрим программу, которая генерирует файл для такого рода объектов. С помощью этой программы мы можем сформировать не только файл с именем EXAMPLE3.DAT, упомянутый в параграфе 1.3, но также и другие файлы для аналогичных конструкций, кото- рые будут иметь иные размеры. Кроме того, мы можем улучшить аппроксимацию цилиндров путем увеличения числа граничных граней (очевидно, что за счет увеличения времени на вычисле- ния) . Если читатель больше заинтересован в результате вычис- ления линии пересечения между двумя цилиндрами, чем в
180 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ программировании, то можно просто использовать эту програм- му, не разбираясь в деталях программирования, обсуждаемых в этом параграфе. Но этот параграф можно рассматривать как до- полнительную демонстрацию возможности написания програм- мы для генерации объектного файла. На рис. 3.20 показан вид сверху на исследуемый объект. Пользователь должен задать четыре размера для работы про- граммы: d — диаметр малого цилиндра; D — диаметр большого цилиндра (D>d); I — длина малого цилиндра (см.рис. 3.20); L — длина большого цилиндра (L > d). (Заметим, что термины большой и малый для цилиндров не дол- жны восприниматься слишком буквально, поскольку длина / "малого" цилиндра может быть больше, чем остальные три раз- мера) . Разместим точку начала О системы координат в точке пересе- чения осей обоих цилиндров. Ось у будет совпадать с осью боль- шого цилиндра, а положительная полуось х с осью малого цилин- дра, как показано на рис. 3.21. Как обычно, круговое сечение обоих цилиндров будем апп- роксимировать многоугольником. Обычно такие сечения явля- ются правильными. Но здесь это будет верно только для малого Рис. 3.20. Вид сверху на пересекающиеся цилиндры
3.11. ПЕРЕСЕЧЕНИЕ ЦИЛИНДРОВ 181 Рис. 3.21. Перспективная проекция пересекающихся цилиндров цилиндра, но не применимо для большого. Это происходит из-за того, что нам желательно, чтобы параллельные линии на малом цилиндре пересекались также с параллельными линиями на большом цилиндре. Например, на рис. 3.21 передняя грань мало- го цилиндра аппроксимируется шестиугольником с точкой 2 в качестве одной из вершин. Прямая линия через эту точку, параллельная оси х пересекает большой цилиндр в точке 8 и от- резок прямой линии 8-14 используется в качестве ребра на боль- шом цилиндре. То есть позиции вершин 13,14,15, 19 на рис. 3.21 определяются позициями вершин 1, 2, 3, 4 на малом цилиндре и поэтому расстояния между этими двумя точками не одинаковы, что хорошо видно из рис. 3.22. Пользователь программы (с именем TCYL) должен ввести число л, равное количеству точек на половине малой окружно- сти, то есть для рис. 3.21 это будет п = 3. (Очевидно, что на прак- тике число п всегда будет выбираться больше, чем 3). На боль- шой окружности нам известна позиция точки 13, которая опре- деляет угол а, показанный на рис. 3.22. Затем дополнение этого угла до 180° разделим на п равных частей, что в сумме дает 2п секторов, каждый с углом п и п неравных секторов в пределах оставшегося угла 2а. Вместо рис. 3.22 лучше использовать рис. 3.23, на котором нумерация вершин поясняется в более общем виде. Вершины на
182 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ Рис. 3.23. Нумерация вершин в пересекающихся цилиндрах передней грани малого цилиндра нумеруются в порядке 1,2, ..., 2л. Следующие 2л чисел 2л + 1, 2л + 2, ..., An приписываются вершинам на линии пересечения двух цилиндров. Затем номера вершин An + 1, An + 2, ..., 5п даются точкам, которые лежат на правой плоскости грани большого цилиндра. Соответствующие точки на левой грани имеют номера 5п + 1, 5п + 2,..., 6/г. После этого приписываются номера оставшимся точкам на двух пло- ских гранях 6л +1, Ьп + 2,..., 8л для правой грани и 8л +1, 8л + 2, ..., Юл для левой грани.
3. J1. ПЕРЕСЕЧЕНИЕ ЦИЛИНДРОВ 183 Вычисление прямоугольных координат для всех этих вершин — сравнительно простая задача, более сложно найти точное об- щее выражение для номеров вершин на каждой грани, особенно из-за того, что не все части большого цилиндра обрабатываются одинаковым образом. Существует забавный момент, связанный с возможностью исключения "искусственного" ребра в окончательном результа- те, когда должны быть пропущены невидимые линии, что было показано на рис. 1.13 (б) и (в) и обсуждалось в параграфе 1.5. Эта опция работает точно только в том случае, если такое ребро в объектном файле задано дважды и если оба появления одного и того же ребра даются в терминах одинаковой пары номеров вер- шин. Например, отрезок прямой 7-13 на рис. 3.21 является ребром многоугольника 8-14-13-7 и вместо объединения двух отрезков прямой 16-7 и 7-13 в одну сторону прямоугольника 16-13-24-30 лучше представить этот прямоугольник в виде ге- нерируемого программой пятиугольника 13-24-30-16-7. Этим объясняется появление в объектном файле двух граней с номера- ми пяти вершин. В Программе TCYL выделены параграфы, формирующие, различные части поверхности объекта. Эти параграфы снабжены комментариями, которые в сочетании с рис. 3.23 делают текст программы ясным и понятным. /* TCYL: Препроцессор программы D3D для вычерчивания Т-образного объекта, состоящего из двух пересекающихся цилиндров. */ #lnclude<stdio.h> #include<math.h> #include <process.h> #include<alloc.h> void wri(lnt a); void wr4(int a, int b, int c, int d); void endcode(void); FILEMp; main() { charfllnam[30]; int i, n, n2, n3, n4, n5, n6, n8, n9, n10, tablelength; float a, b, I, L, D, d, R, r, alpha, theta, delta, *x, *y, *z, pi, beta;
184 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ pi-4.0*atan(1.0); printf("\n\n\n Схема Т-образного соединения труб\п"); printf( /* length L, diameter D */ "\n \n"); printf(" ( i ) длина Ц диаметр D\rT); printf(" ll\n"]t; printf(" ll\n"); printf(" 11 длина I, диаметр d\n"); printf(" 11 \n"); /* length I, diameter d */ printf(" ll\n"); do { phntf(" (d< D, d<L)\n\n\n"); prlntf /* "Enter d, D, I, L In that order:" */ ("Введите значения d, D, I, L именно в этом порядке\пп); scanf("%f %f %f %f\ &d, &D, &l, &L); }while(d-D lld-L); R-D/2;r«d/2;a-l + R;b-L/2; do { printf("\n(d<D, d<L)\n\n"); prlntf /* "Enter d, D, I, L In that order */ ("Введите значения d, D, I, L именно в этом порядке: \п"); scanf("%f %f %f %f", &d, &D, &l, &L); }whlle(d>-D lld>-L); R-D/2;r-d/2;a-l + R;b-L/2; prlntf /* The circular boundary face with diameter d will be */ ("ХпОснование кругового цилиндра с диаметром d, будет\п"); printf /* approximated bye regular polygon of 2n sides. */ ("аппроксимироваться правильным 2п-многоугольником\п\п"); printf /* "Enter n:"*/ ("Введите число п:"); scanf("%d", &n); printf /* "Name of output file:" */ ("Имя выходного файла:"); scanf("%s", filnam); fp-fopen(filnam, "w"); If (fp - - NULL){printf /* "File problem" */ ("Проблема с файлом"); exit(1);} n2-2*n; n3-3*n; n4-4*n; n5-5*n; п6-6*п; п8-8*п; п9-9*п; пЮ- 10*п; tablelength - (п 10 + 1) * sizeof(float); х-(float *)malloc(tablelength); у-(float *)malloc(tablelength); z- (float *)malloc(tablelength); if (z - - NULL){printf /* "Not enough memory" */ ("He хватает памяти"); exit(1);} theta-pi/n; for(l-1;i<«n2;i++) { x[l]-a; y[i]-r*sin((i-1)*theta); z[i]-r*cos((i-1)*theta); }
i. 11. ПЕРЕСЕЧЕНИЕ ЦИЛИНДРОВ 185 for(i-n2+1;i<-n4;i++) { y[i]-y[i-n2];z[i]-z[i-n2]; x[i]«sqrt(R*R-z[i]*z[i]); } for(i-n4+1;i<«n5; i++) { x[i]-x[i-n2];y[i]-b;z[i]-z[i-n2]; } for(i-n5+1;i<-n6;i++) { X[i]-x[i-n];y[i]— b;z[i]-z[i-n]; } alpha - atan(z[n4+1]/x[n4+1]); delta - (pi - alpha)/n; for(i-n6+1;i<-n8;i++) { beta-alpha+ (i-n6-1)*delta; x[l]- R * cos(beta); y[i]- b; z[i] = -R * sin(beta); } for(i-n8+1;i<-n№;i++) { x[l]-x[i-n2];y[l] = -b;z[i]-z[l-n2]; } for(i-1;i<-n10; i++) fprlntf(fp, "%d %f %f %f\n", i, x[i], Ш z[i]); fprintf(fp, "\nFaces- ГраниЛп"); /* Smaller cylinder, curved surface: /* Малый цилиндр, криволинейная поверхность for(i-1; Kn2; i++) wr4(i, i+1, i+n2+1, i+n2); wr4(n2,1.n2+1.n4); /* Larger cylinder, pieces on curved surface to the right */ /* of smaller cylinder; from top to bottom: */ /* Большой цилиндр, часть криволинейной поверхности */ /* справаот малого цилиндра сверху вниз */ for(i-n2+1; КпЗ; i++) wr4(i, 1+1, i+n2+1, i+n2); wr4(n3,n3+1,n6+1, n5); /* Larger cylinder, pieces on curved surface to the left */ /* of smaller cylinder; from bottom to top: */ /* Большой цилиндр, часть криволинейной поверхности слева */ ■ /* от малого цилиндра снизу вверх */ wr4(n3+2, пб, п8+1, пЗ+1); for (i-пЗ+З; i<-n4; i++) wr4(i, n9+2-i, n9+3-i, i-1); wr4(n2+1, n5+1. n5+2, n4); /* Larger cylinder, large rectangular pieces on curved */ /* surface, starting near the smaller cylinder's lowest point: */ /* Большой цилиндр, большие прямоугольные участки на на */ /* криволинейной поверхности, начинающиеся вблизи самой */ /* нижней точки малого цилиндра */ wri(n6+2); wr4(h6+1, пЗ+1, п8+1, п8+2); for(i-n6+3; K-n8; i++) wr4(i, i-1, i+n2-1, i+n2); wri(n4+1); wr4(n8. n10, n5+1, n2+1); V V
186 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ /* Flat front surface of smaller cylinder: /* Плоская передняя поверхность малого цилиндра for(i-n2;l>0;l--)wrl(l); endcodeQ; /* Right-hand flat surface of larger cylinder. /* Плоская правая поверхность большого цилиндра for (l-n4+1; K-n5; i++) wrl(l); /* Front portion /* Передняя часть for (1-п6+1; K-n8; i++) wrl(l); /* Remaining portion /* Оставшаяся часть endcode(); /* Left-hand flat surface of larger cylinder. /* Плоская левая поверхность большого цилиндра for (l-пб; I>n5; I- -) wrl(l); /* Front portion /* Передняя часть for(l-n10; 1>п8; i--)wri(i); /* Remaining portion /* Оставшаяся часть endcodeQ; fclose(fp); } void wri(int a) { fprlntf(fp,M%d",a); void wr4(lnt a, Int b, Int c, Int d) { fprintttfp.M %d %d %d %d.\nM, a, b, c, d); void endcode(vold) { fprlntf(fp,".\n"); 3.12.ПРОГРАММА ДЛЯ ПРЯМОУГОЛЬНОЙ ВИНТОВОЙ РЕЗЬБЫ Теперь рассмотрим программу SCRTHR, использовавшуюся в параграфе 2.6. Напомним, что она формирует объектный файл для прямоугольной винтовой резьбы, показанной на рис. 2.21. Не будем здесь повторять обсуждение параграфа 2.6, а сфокусируем свое внимание на нумерации вершин и аппроксимации кривых поверхностей плоскими гранями. Начнем с терминов, которые будут применяться в нашем об- суждении. Напомним, что задаются большой диаметр D и малый (внутренний) диаметр d. В программе (с математической точки */ */ */ */ V V */ */
3.12. ПРОГРАММА ДЛЯ ПРЯМОУГОЛЬНОЙ ВИНТОВОЙ РЕЗЬБЫ 187 зрения) обычно более предпочтительно вместо диаметра иметь радиус, поэтому определим: Д = £>/2, г-с//2 Ось винтовой резьбы будет совпадать с положительным на- правлением оси z, то есть винт располагается вертикально. Будем различать элементы винтовой резьбы: 1. Внешняя поверхность, все точки которой располагаются на расстоянии R от оси винта. 2. Внутренняя поверхность, все точки которой лежат на рас- стоянии гот оси. 3. Верхняя грань, плоская поверхность, лежащая в плоскости *z = L. 4. Нижняя грань, плоская поверхность, лежащая в плоскости xy(z = 0). 5. Верхняя сторона, кривая поверхность, частично видимая на рис. 3.24, которая соединяет внешнюю и внутреннюю по- верхности. 6. Нижняя сторона, кривая поверхность, аналогичная верх- ней стороне и частично видимая на рис. 3.25. На винтовой резьбе каждое кривое, не горизонтальное, ребро является так называемой спиралью. Для определенности про- цесса аппроксимации пользователь программы SCRTHR должен ввести число к — количество шагов на половину оборота, при этом обеспечивается, что число шагов на полный оборот п = 2к будет четным. Поскольку заданный шаг винтовой резьбы соот- ветствует прямому перемещению винта при полном обороте, то есть за п шаюв, координата z увеличивается на deltaL=JE£h- п при перемещении по спирали на один шаг. Заданная длина LQ округляется до ближайшего значения, кратного целому числу delta L, которую обозначим через L. Впоследствии будет часто использована константа N'inb:+l
188 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ которая соответствует числу вершин на каждой спирали. Прону- меруем эти вершины следующим образом (см. рис. 3.24 и 3.25) снизу вверх: 1, 2,..., N: Спираль 1, пересечение внешней поверхности и верхней стороны; jV+1,N + 2,..., 2N: Спираль2, пересечение внутренней по- верхности и верхней стороны; 2./V+ 1, 27V + 2,..., 3N: Спираль 3, пересечение внешней поверхности и нижней стороны; 3N + 1, 3iV + 2,..., 4N: Спираль 4, пересечение внутренней поверхности и нижней стороны. Рис. 3.24. Винтовая поверхность, аппроксимируемая плоскими гранями: перспективный видсверху Рис. 3.25. Винтовая поверхность, аппроксимируемая плоскими гранями: перспективный вид снизу
J.12. ПРОГРАММА ДЛЯ ПРЯМОУГОЛЬНОЙ ВИНТОВОЙ РЕЗЬБЫ 189 Кроме этих четырех спиралей нам нужно рассмотреть верх- нюю и нижнюю грани, на каждой из которых находятся две по- луокружности, одна из них с радиусом R> другая — с радиусом г. На каждой из этих четырех полуокружностях имеется к - 1 то- чек, которым еще нужно присвоить номера вершин, поскольку они лежат на спирали. Для этого введем обозначения Л = 4ЛГ-1 # = Л + *-1=4ЛГ + *-2 С = £ + *-1=4ЛГ+2*-3 £> = С + *-1 = 4ЛГ+3*-4 что показано на рис. 3.24 и 3.25: А + 2, А + 3,..., А + к: Оставшиеся точки на пересечении внешней поверхности и нижней грани, Б+ 2, Я + 3,..., В + к: Оставшиеся точки на пересечении внутренней поверхности и нижней грани, С+ 2, С + 3,..., С + Л: Оставшиеся точки на пересечении внешней поверхности и верхней грани, Z> + 2, £ + 3,..., D + k: Оставшиеся точки на пересечении внутренней поверхности и верхней грани. Начиная снизу, первые 180° и последние 180° внешней по- верхности следует рассматривать несколько иначе, чем осталь- ную ее часть (которую будем называть средней частью). То же самое относится и к внутренней поверхности. Средние части обеих поверхностей, внешней и внутренней, будем аппроксими- ровать параллелограммами, каждый из которых имеет две вер- тикальные стороны. Первая часть внешней поверхности будет треугольником с номерами вершин 1, А + 2, 2. Затем будут следо- вать к - 1 трапеций: они имеют две боковые вертикальные сторо- ны неравной длины. Аналогично последним 180°, самая послед- няя часть внешней поверхности будет треугольником с вершина- ми 37V, С + к, 3N - 1. Подобные условия применимы и для внут- ренней поверхности, которая начинается с треугольника В + 2, 3N + 2, 3N + 1 и заканчивается треугольником D + k,2N-l, 2N. Кажется странным, что на рис. 3.24 и 3.25 верхняя и нижняя стороны винтовой резьбы показаны поделенными на треугольни- ки: использование четырехугольников вроде бы проще и более естественно. Действительно, автор может признаться в этом, что в первой версии программы была допущена такая же ошибка.
190 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ Попытка определить часть поверхности 1, 2, N + 2, N + 1, пока- занную на рис. 3.24, в виде одного полигона привело в результате к появлению сообщения об ошибке из программы D3D, утверж- давшего, что не все вершины указанного полигона лежат в одной плоскости. Это можно объяснить следующим образом. Вершины 1 и N + 1 имеют одинаковую координату z (z = 0), то же самое относится к вершинам 2 и N + 2 (z = pitch/n). Таким образом две прямые линии (1, N + 1) и (2, N + 2) лежат в двух различных горизонтальных плоскостях. Это означает, что эти две горизон- тальные прямые линии должны бы быть параллельными, если бы упомянутые четыре точки лежали в одной плоскости (поскольку любая не горизонтальная плоскость будет иметь параллельные линии пересечения с двумя горизонтальными плоскостями). Яс- но, что две рассматриваемые горизонтальные прямые линии не параллельны, поэтому четыре рассматриваемые точки не лежат в одной и той же плоскости. Это также доказывает, что два отрез- ка прямой линии (1, 2) и (N + 1, N + 2) не параллельны (первый имеет меньший наклон, чем последний). Наконец, нижняя и верхняя грани определяются невыпуклы- ми полигонами, каждая из которых может быть определена как одна ограничивающая грань: Их описание, как это видно из рис. 3.24 и 3.25, составить совсем не трудно, за исключением некото- рой сложности из-за отсутствия непрерывности в последователь- ности номеров вершин. Вполне очевидно, что автор не распола- гал такими иллюстрациями, когда начинал разработку програм- мы SCRTHR, даже при наличии реальной модели или фотогра- фии плоского конца винтовой резьбы, так что потребовалось зна- чительное время,. чтобы нарисовать правильную форму этих двух концевых граней. С приведенной нумерацией точек программа SCRTHR стала сравнительно простой. Может быть полезно упомянуть, что вна- чале автор* использовал совершенно иную систему нумерации точек и написал такую сложную программу, что впоследствии и сам не мог в ней разобраться, когда понадобилось написать эту книгу. Однако программа работала правильно, что позволило сделать несколько изображений, подобных рис. 3.24. И уже на их основе была выбрана нумерация вершин и разработана совер- шенно новая программа SCRTHR, текст которой и приводится ниже.
3.12. ПРОГРАММА ДЛЯ ПРЯМОУГОЛЬНОЙ ВИНТОВОЙ РЕЗЬБЫ 191 /*SCRTHR: */ /* Винтовая поверхность */ ♦include <stdio.h> #include<math.h> ♦include <ctype.h> ♦include <alloc.h> ♦include <process.h> struct point {float x, y, z;} *p; FILE*fp; voidpl(inti); voldple(inti); void p3e(int i, intj, Intk); void p4e(lnt I, int J, int k, int I); void pr(lnta, Int b); main() {int I, J, n, N, N2, N3, N4, k, steps, A, §, C, D; double pi, theta, phi, R, r, deltaL, major, minor, pitch, h, L, LO, offset; charstr[30],ch; pi-4*atan(1.0); printf ("Большой диаметр:м); scanf("%lf", &major); printf ("Малый диаметр:"); scanf(M%ir, &mlnor); printf ("Если профиль резьбы должен быть квадратом, то\пп); printf (" шаг - большой диаметр - малый диаметр - %5.3Лп\п", pitch - major - minor); printf ("Тогда при одном обороте параметр 'шаг' определяет"); printf (" расстояние,\пна которое перемещается винт,"); printf (" поэтому воображаемый 'квадратЛпбудет иметь"); printf (" длину стороны h - шаг/2 - %5.3f\n\n", pitch/2); printf ("Желаете ли задать другую величину шага, чтобы профиль\п"); printf ("резьбы был не квадратным (но прямоугольным)? (Y/N):"); scanf("%s",str); ch - toupper(str[OJ); If(ch--'Y') { printf ("Шаг:"); scantXM%lf". &pitch); } h-pitch/2; printf ("Желаемая длина винтовой резьбы не менее, чем %5.3f: ", h); scanf("%lf", &L0); If (LO < h) LO - h; /* По крайней мере половина оборота */ printf ("Число точек на половине оборота винта:м); scanff%d",&k); n-2*k; deltaL— pitch/n; steps-(intXLO/deltaL+0.5); N-steps+1; N2-2*N;N3-3*N;N4-4*N;
192 Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ L-steps * deltaL; printf ("Фактическая длина будет - %5.3f\n\n", L); printf ("Имя выходного файла: "); scanfCJ&s", str); fp-fopen(str. "w"); theta-pi/k; R - major/2; r- minor/2; A-4*N-1; B-A + k-1; OB + k-1; D-C + k-1; p-(struct point *)malloc((D+k+1)*sizeof(struct point)); if (p--NULL){printf ( "He хватает памяти"); exit(1);} for (i-1; i<-N; I++) /* Внешняя спираль на верхней поверхности */ { phi-(i-1)*theta; p[!].x-R*cos(phi); p[l].y-R*sin(phi); p[i].z-(i-1)* deltaL; } for(i-N+1; K-N2; i++) /* Внутренняя спираль на верхней поверхности */ { p[l].x-p[i-N].x*r/R; p[i].y-p[i-N].y*r/R; p[l].z-p[i-N].z; } for (i—N2+1; K-N3; i++) /* Внешняя спираль на нижней поверхности */ { P[i]x— p[i-N2].x; p[i].y—p[i-N2].y; p[l].z-p[i-N2].z; } for(i-N3+1; K-N4; i+f) /* Внутренняя спираль на нижней поверхности */ { p[i].x-p[i-N].x*r/R; p[i]y-p[i-N].y*r/R; p[i].z-p[l-N].z; } for(i-A+2;i<-A+k;i-H-) /* Внешняя полуокружность на нижней плоскости */ { phi-(i-A-1)*theta; p[i].x-R*cos(phi); p[i].y-R*sin(phi); p[i].z-0; } for(i-B+2;i<-B+k;i++) /* Внутренняя полуокружность на нижней плоскости */ { p[i].x—p[i-B+A].x * r/R; p[i].y—p[i-B+A].y*r/R; p[i].z-0; } offset - steps * theta; while (offset > 2*pi) offset — 2*pi; for(i-C+2; i<-C+k; i++)
3.12. ПРОГРАММА ДЛЯ ПРЯМОУГОЛЬНОЙ ВИНТОВОЙ РЕЗЬБЫ 193 /* Внешняя полуокружность на верхней плоскости */ { phi - offset + (i-C-1)*theta; p[i].x-R*cos(phi); p[i].y-R*sin(phi); P[i]z-L; } for(i-D+2;i<-D+k;i++) /* Внутренняя полуокружность на верхней плоскости */ { p[l].x— p[i-D+C].x*r/R; p[i].y— p[i-D+C].y*r/R; p[l].z-L; } for(i-1;i<-D+k;i++) fprintf(fp1"%4d%f%f%f\n'\ i. P[i]x. p[i].y, p[i].z); fprintf(fp, "Faces-граниЛп"); /* Грани */ /*z-0:V p1(N2+1); pr(A+k, A+2); p1(T); p1(N+1); pr(B+k.B+2);p1e(N3+1); /*z-L:*/ p1(N); pr(C+2, C+k); p1(N3); p1(N4); pr(D+2.D+k);p1e(N2); /* Первые 180 градусов (снизу) */ /* Внешняя поверхность */ рЗе(1,А+2,2); for (j-2; J<k; J++) p4e(A+j, A+J+1, j+1, j); p4e(A+k,N2+1,k+1,k); /* Внутренняя поверхность */ рЗе(В+2, N3+2, N3+1); for (J-2; j<k; j++) p4e(B+j+1, N3+J+1, N3+J, B+j)r p4e(N+1, N3+k+1, N3+k, B+k); /* Последние 180 градусов (сверху) */ /* Внешняя поверхность */ p3e(N3, C+k, N3-1); for (j-2; j<k; J++) p4e(C+k-j+2, C+k-j+1, N3-j. N3-J+1); p4e(C+2, N. N3-k, N3-k+1); /* Внутренняя поверхность */ p3e(D+k,N2-1,N2); for 0-2; j<k; j++) p4e(D+k-j+2, D+k-j+1, N2-j, N2-J+1); p4e(D+2, N4, N2-k, N2-k+1); /* Средняя часть */ /* Внешняя поверхность */ for 0-1; j<N-k; j++) p4e(N2+j, N2+J+1, k+j+1, k+j);
194 % Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ /* Внутренняя поверхность */ for 0-1; J<N-k; j++) p4e(N+J. N+J+1, N3+K+J+1, N3+k+J); /* Верхняя сторона */ for0-1;J<N;J++) { p3eQ,N+J+1,N+J); p3e(N+j+1J,J+1); } /* Нижняя сторона */ for(J-1;J<N;j++) { p3e(N3-J+1, N4-j, N4-J+1); p3e(N4-j,N3-j+1,N3-J); } f close (fp); voldpl(lntl) { fprlntf(fp,u%dM); } void p1e(lnt I) { fprintttfp,"%d.\n".i); } voidp3e(lntl, IntJ.lntk) { fprlntf(fp,M%d%d%d.\nM,J,k); } void p4e(int I, int j. int k, int I) { fprlntttfp.u %d %d %d %d.\nw, I, j, k, I); } voldpr(inta, intb) { Int I; If (b >-a) for(l-a; i<-b; I++) fprlntf(fp,tt %dM, I); else for (I-a; l>-b; I--) fprlntf(fp, M %dM, I); }
Часть III ДЕТАЛИ ГРАФИЧЕСКИХ ПРОГРАММ Глава 4 ПРОГРАММЫ D3D И PLOTHP 4.1. ВВЕДЕНИЕ В этой главе в основном пойдет речь о программе D3D, кото- рая намного больше, чем все другие программы, обсуждавшиеся до сих пор. Ее выполняемая версия D3D.EXE является резуль- татом совместной компоновки четырех объектных модулей D3D.OBJ, GRPACK.OBJ, HLPFUN.OBJ и TRAFO.OBJ, получен- ных с помощью компилятора Турбо Си при компиляции соответ- ствующих четырех исходных файлов. Описание каждого из этих четырех модулей и сами листинги исходных текстов программ можно найти в следующих параграфах: Модуль Описание Листинг D3D параграф 4.2 Приложение А GRPACK параграф 4.3 параграф 4.3 HLPFUN параграф 4.4 Приложение Б TRAFO параграф 3.7 параграф 3.7 Напомним, что в параграфе 3.7 уже обсуждался вопрос о по- вороте объектов в трехмерном пространстве в контексте аппрок- симации кривых и построения изображений кабеля. Снова ока- жутся полезными функции, которые там применялись (initrotate и rotate в модуле TRAFO). Программа D3D слишком большая, чтобы ее подробно описы- вать в этой книге, поэтому здесь будут обсуждены лишь некото- рые ее аспекты. Если читатель захочет узнать о ней несколько больше, чем дается в тексте параграфа 4.2, то он может обра- титься непосредственно к исходному модулю в Приложении А. 7**
196 Глава 4. ПРОГРАММЫ D3D И PLOTHP В параграфе 4.3. будет рассмотрен модуль GRPACK, пред- ставляющий собой пакет графических подпрограмм, полезный не только в связи с программой D3D, но также и для многих дру- гих целей. Он подобен графическому пакету с таким же именем, описанному в книге автора "Машинная графика на персональ- ных компьютерах". Программы в той книге были ориентирова- ны на цветной графический адаптер (CGA), и графический адап- тер Геркулес (HGA), тогда как данная версия пакета GRPACK поддерживает также и расширенный графический адаптер (EGA). Кроме того, новая версия пакета GRPACK основана на компиляторе Турбо Си, недоступном автору при написании пре- дыдущей книги. Другим новым аспектом является возможность вывода графической информации в виде файла, который впос- ледствии может быть считан другой программой для получения твердой копии, например, с помощью плоттера. Пример такой программы приведен в параграфе 4.5. В параграфе 4.4 обсудим программу HLPFUN — функцию для удаления невидимых линий. Она основана на методике, кото- рая применялась в модуле HIDLINPIX — программе, игравшей центральную роль в книге автора "Принципы программирова- ния в машинной графике". Как расширение предыдущей про- граммы HLPFUN предлагает средство для обработки кривых по- верхностей, она отличается от исходной программы и в других аспектах. Например, память компьютера используется более гибким способом, она не рекурсивна и тем самым исключается любая опасность переполнения стека. До сих пор речь шла о модулях, которые, после компиляции и редактирования связей, образуют программу D3D. В параграфе 4.5 описывается отдельная, самостоятельно работающая про- грамма PLOTHP. Она может сформировать изображение на плоттере фирмы Хьюлетт-Паккард, с помощью этой программы были получены многие иллюстрации в этой книге. Эта програм- ма может оказаться полезной и в случае отсутствия такого плот- тера, поскольку ее можно использовать для пересылки графиче- ского вывода как на видеодисплей, так и на матричный принтер. Кроме того, файл, записанный с применением опции О (совмест- но с командами Н и Е), может быть считан программами, напи- санными самостоятельно для получения вывода на другие графи- ческие устройства, например, на лазерный принтер. Другими
4.2. ОСНОВНОЙ МОДУЛЬ ПРОГРАММЫ D3D 197 словами, программу PLOTHP можно рассматривать только как пример отложенного вывода графической информации. 4.2. ОСНОВНОЙ МОДУЛЬ ПРОГРАММЫ D3D Перед тем, как рассматривать саму программу D3D, обсудим в общих чертах возможность получения перспективных изобра- жений пространственных объектов вычислительным способом. Будем в дальнейшем использовать центральную точку объекта О, лежащую более или менее вблизи центра в объекте, который желаем изобразить в перспективе. Другая важная точка — точка наблюдения Е, заданная своими сферическими координатами р, 0, <р, как упоминалось в параграфе 1.2. Эти сферические коор- динаты относятся к осям правой системы прямоугольных коорди- нат, полученной путем переноса пользовательской системы мировых координат таким образом, что точка О становится началом этой системы координат. Тогда значение р будет равно отрезку ЕО. Для каждой вершины объекта имеем мировые координаты xw, yw, zw, выраженные в системе пользовательских координат, начало которой может и не совпадать с точкой О). Как читатель вероятно помнит, ось z каждой из этих двух систем координат направлена вверх, а ось х обычно направлена к наблюдателю. Мы будем использовать мировые координаты *w' ^w' zw для вычисления видовых координат *е, уе, ze. Система видовых координат является левой, ее начало расположено в точке наблюдения Е, положительная полуось z указывает на центральную точку объекта О, ось х направлена горизонтально вправо, а ось у (перпендикулярная осям х и z) указывает вверх. Видовые координаты получаются в результате так называемых видовых преобразований: В этом матричном умножении используется матрица 4x4 F-sin в -cos (p cos в -sin <p cos в 0 ] cos0 -cos/> sin0 -sin <p sin в 0 О sin^> -cos^> О [ 0 0 р 1] Заметим, что в выражении для матричного умножения мож- но опустить члены -jcq, -y0, -zQ, если они должны быть равны
198 Глава 4. ПРОГРАММЫ D3D И PLOT HP нулю, то есть в случаях, когда мировые координаты xw, yw, zw от- носятся к перенесенной системе координат (с началом в точке О) вместо пользовательской системы координат. Видовые преобразования являются только первым из двух шагов, которые необходимо выполнить. Будем использовать видовые координаты для вычисления двухмерных экранных координат jcs, ys, которые необходимы для получения желаемого перспективного изображения. Они находятся на втором шаге при выполнении перспективного преобразования: xs = d*xe + cl ys = d*yt + c2 Отсюда ясно, что параметр d является коэффициентом масш- табирования, а параметры сх, с2 используются для переноса изо- бражения по горизонтали и вертикали соответственно для разме- щения в нужной позиции. В предыдущей книге автора "Принципы программирования в машинной графике" было показано, как можно найти матрицу V в виде произведения некоторых других матриц. В ней также име- ется параграф об автоматическом подборе размера и позиции для заданной области вывода (то есть используемой части экрана для вывода изображения). Это реализовано и в данной версии. Не забывайте, что при использовании программы D3D мы мо- жем ввести новые точки, которые не попадут внутрь заданной области вывода. В таком случае нам необходимо будет найти но- вые значения констант сх, с2 и d. Они должны быть выбраны так, чтобы все изображение целиком попадало в пределы границ эк- рана. Внутри программы сохраняются не только мировые коор- динаты х. v , z. но и видовые координаты * , >> , zp. Это значи- тельно сокращает объем необходимой работы для подбора разме- ра изображения, поскольку новые значения cv c2 и d потребуют- ся только для перспективного преобразования, когда эти дейст- вия должны быть выполнены для всех вершин. Но при этом более трудоемкие операции видовых преобразований окажутся не нужными. В упомянутой выше книге "Принципы программирования в машинной графике" применялся такой формат входных файлов, когда координатам всех вершин объекта должны предшествовать координаты jc0, y0, z0 его центральной точки О. Сейчас же мы находимся совершенно в иной ситуации и имеем дело с интерак-
4.2. ОСНОВНОЙ МОДУЛЬ ПРОГРАММЫ D3D 199 тивной программой, которая воспринимает новые точки в любой момент и которая не требует от пользователя указывать заранее положение центральной точки. Положение центральной точки объекта вычисляется по набору точек (заданным либо в объект- ном файле, либо вводимом вручную) по формулам х0 - (xwmin + xwmax) /2 у0 = (ywmin + ywmax) /2 z0 = (zwmin + zwmwc)/2 где в качестве xwmin и xwmax используется наименьшие и наи- большие значения мировых координат по оси х и аналогично по другим осям. Таким образом точка О будет находиться точно в середине наименьшей прямоугольной призмы (с гранями, парал- лельными координатным осям в системе мировых координат), внутри которой лежат все заданные точки. Будем называть такую призму ограничивающей призмой. Если будет введена новая точка, лежащая вне ограничивающей призмы, то, строго говоря, нужно было бы вычислить новую цен- тральную точку объекта. Но тогда потребовалось бы заново вы- числить положение точки наблюдения хе, уе, ze, поскольку точка О влияет на систему видовых координат. Однако мы знаем, что положение точки О не очень критично до тех пор, пока она нахо- дится где-то вблизи центра объекта. Поэтому оставим позицию точки О неизменной, если она находится на некотором разумном расстоянии от центра ограничивающей призмы. Когда это рас- стояние, называемое "допуском", будет превышено, тогда нам придется действительно изменить точку О, примирившись с воз- можными большими затратами времени на вычисление новых видовых координат для всех вершин. В программе D3D приняты следующие значения допусков для направлений по всем координатным осям: xtol = 0.4 (xwmax - xwmin) ytole 0.4 (ywmax - ywmin) ztol» 0.4 (zwmax - zwmin) Итак, внутри ограничивающей призмы можно представить себе некоторую центрально симметричную призму, полученную пу- тем масштабирования ограничивающей призмы с коэффициен- том 0.8. Пока точка О находится внутри этой малой призмы ее положение можно считать допустимым.
200 Глава 4. ПРОГРАММЫ D3D И PLOTHP Центральной частью программы D3D является цикл типа while, в котором многократно производится считывание первой буквы команды, вводимой с клавиатуры и выполняются соответ- ствующие действия. Эти действия выполняются путем вызова нужных функций. Например, если пользователь вводит команду F (или/), то вызывается функция faces. Все функции, в том чис- ле и faces, располагаются в алфавитном порядке, что упрощает их поиск. Все вершины запоминаются в таблице; вместо нор- мальных массивов применяется указатель р, который позволяет использовать таблицы такой длины, которая зависит от доступ- ного объема памяти. Для каждой вершины в памяти хранятся как мировые координаты (jcw, yw, zw) так и видовые координаты (*е> Уе> ze)> что обсуждалось выше. Положение текущей цент- ральной точки объекта проверяется в функции reposition. Если эта позиция неприемлема, то центр ограничивающей призмы ис- пользуется в качестве новой точки О и для всех вершин вычисля- ются новые видовые координаты (jce, ye, zQ) путем вызова функ- ции storepointy которая в свою очередь вызывает функцию viewing. После возврата в функцию reposition происходит обра- щение к функции screencoor, изменяющей при необходимости значения переменных xmin, хтах, ymin, ymax (не нужно их пу- тать с переменными xwmin и так далее). Они используются в ка- честве минимальных и максимальных значений выражений xjze и ye/ze> которые появляются при перспективных преобразо- ваниях и они дают нам возможность вычислить важные "кон- станты" Cj, с2 и d, также появляющиеся в перспективных преоб- разованиях. При Xrange = хтах - xmin Yrange = ymax - ymin Xcenter = (xmin + xmax) /2 Ycenter = (ymin + ymax) /2 и позиций центра (Xvp_center, Yvpjcenter) и размеров Xvpjrange и Yvpjrangejyji* заданной области вывода, будем иметь: - _ Xvpjrange х Xrange . _ Yvpjrange h Yrange
4.2. ОСНОВНОЙ МОДУЛЬ ПРОГРАММЫ D3D 201 d = наименьшее из двух значений /х и / Cj = Xvpjcenter - d * Xcenter c2 = Yvp_center - c/ * Ycenter как было получено в параграфе 2.6. книги "Принципы програм- мирования в машинной графике". Как следует из сказанного, здесь приходится иметь дело с гра- ницами как в трехмерном, так и в двухмерном пространствах. Что касается трехмерного пространства, то нужно побеспокоить- ся лишь о том, чтобы ограничивающая призма (определяемая значениями xwmin, xwmax, ywmin, ywmax, zwmin, zwmax) редак- тировалась каждый раз при добавлении точки, лежащей вне ее. Двумерные границы задаются значениями xmin, хтах, ymin, утаХу которые определяют область вывода. При вводе каждой новой точки нужно проверять, что ее двухмерное отображение лежит внутри этих двухмерных границ. В функции plotpoint вызывается функция screencoor, которая может устанавливать в 0 глобальную переменную inside как сигнал, что двухмерная проверка дала неудовлетворительный результат. Тогда вызы- вается функция reposition и сначала выполняется проверка в трехмерном пространстве: если текущая "центральная точка объекта" О лежит слишком далеко от точного центра ограничи- вающей призмы, то она изменяется и для всех вершин вызывает- ся функция storepoint для вычисления новых координат точки наблюдения jce, уе, ze. Независимо от исхода проверки в трехмер- ном пространстве функция reposition вычисляет новые значения Ср с2 и d. После этого при всех последующих обращениях к фун- кции display для обновления изображения на экране будут ис- пользованы эти новые значения и изображения всех точек те- перь будут располагаться внутри области вывода. Итак, при пе- речерчивании изображения на экране оно может совмещаться с вычислением новых видовых координат, а может и нет. Если пересчет не производится, то новое изображение появится на экране значительно быстрее, чем при пересчете. Но и для других обновлений изображения на экране при вычислении только но- вых экранных координат (оставляя неизменными видовые коор- динаты) требуется некоторое время. Теперь предположим, что мы перемещаем курсор в фиксированном направлении, исполь- зуя очень небольшие шаги, и будем продолжать эту операцию до тех пор, пока не будет достигнута граница области вывода. Было
202 Глава 4. ПРОГРАММЫ D3D И PLOTHP бы очень неприятно, если бы потребовалось обновлять все изо- бражение на экране для каждого такого небольшого.шага. Следо- вательно, при использовании рабочей части экрана каждое новое значение d уменьшается на некоторый коэффициент "-допуска", в качестве которого автор выбрал значение 0.85. Это означает, что немедленно после обновления изображения на экране размер изображения на экране будет уменьшено до 0.85 от максималь- ного размера, допускаемого областью вывода. При этом курсор сможет сделать еще несколько шагов, прежде чем снова достиг- нет границы области вывода. Значение 0.85 этого "коэффициен- та допуска" выбрано сравнительно произвольно и при желании его вполне можно изменить (в функции reposition). Чем больше этот коэффициент (но не более 1), тем чаще будет обновляться изображение на экране как следствие перемещения курсора, так что его можно уменьшить, если по вашему мнению обновление изображения на экране происходит слишком часто. Но все это будет делаться за счет того, что каждый раз при обновлении изо- бражения на экране будут увеличиваться неиспользуемые поло- ски вдоль всех четырех сторон границ области вывода, иными словами, размер изображения будет уменьшаться. Можно возразить, действительно ли необходимо выполнять проверки в двухмерном пространстве для всех отдельных точек. Но из всего сказанного следует, что при сохранении условия попадания изображений всех восьми вершин ограничивающей призмы в пределы области вывода гарантируется нахождение внутри области вывода изображений всех точек, лежащих в пре- делах ограничивающей призмы. Конечно этот способ безопас- ный, но, к сожалению он слишком безопасный. Для многих объ- ектов при этом могут быть получены изображения по размерим значительно меньше, чем было бы возможно. Возьмем, к приме- ру, сферу. Ее ограничивающая призма будет представлять собой куб, в который она вписывается. Для большинства направлений наблюдения куб потребует для своего отображения на экране значительно больше места, чем сфера, поэтому не очень хорошо применять размеры куба в качестве основы для перспективного преобразования сферы. Кроме того, при этом способе будет очень часто вызываться обновление изображения на экране для точек (или позиции курсора), которые, хотя и лежат вне текущей огра- ничивающей призмы, но все еще остаются в пределах области
4.3. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ 203 вывода. Но с другой стороны, по нашему методу положение точ- ки сравнивается с областью вывода, а не с ограничивающей призмой. Для ограничивающей призмы допускается иметь изо- бражение, которое не входит в пределы области вывода, она не- обходима только для того, чтобы можно было при необходимости вычислять положение новой центральной точки объекта О. Приведенные замечания никак не заменяют полного объяс- нения программы D3D, но они касаются наиболее существенных элементов этой программы. Наиболее важными составными час- тями программы D3D являются операции по повороту в трехмер- ном пространстве, программное обеспечение графики нижнего уровня и удаления невидимых линий, что было предметом об- суждения в параграфах 3.7, 4.3 и 4.4 соответственно. Все исход- ные модули приведены в Приложении А. Приложение содержит большое количество сравнительно небольших по размеру моду- лей, расположенных в алфавитном порядке. Исходные тексты программ могут оказаться очень полезными, если у читателя возникнет желание узнать, как они работают. 4.3. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ Пять из шести глав предыдущей книги автора ''Машинная графика для персональных компьютеров" (в дальнейшем изло- жении будем ее для краткости обозначать МГПК) были посвя- щены разработке пакета функций на языке Си для машинной графики нижнего уровня, получившего название GRPACK. Ос- новные положения такого рода программного обеспечения отли- чаются от большинства программ в данной книге тем, что оно тесно связано со специфическим оборудованием и версиями ком- пилятора. Когда автор работал над книгой МГПК, то он осозна- вал, что некоторые из функций графики нижнего уровня в ней были не особенно элегантны, что было следствием того, что сами персональные компьютеры фирмы IBM и их базовое программ- ное обеспечение не были достаточно элегантны во всех аспектах. Однако, нам не всегда удается избавиться от машино- и компи- ляторо-зависимых аспектов, если желательно получить реально работающие программы. Функции графики нижнего уровня являются базовыми стро- ительными блоками для графики высокого уровня: как основная программа D3D, так и модуль удаления невидимых линий
204 Глава 4. ПРОГРАММЫ D3D И PLOTHP HLPFUN используют функции нижнего уровня, обсуждаемые в данном параграфе. Ниже будем описывать и использовать модифицированную версию упомянутого выше пакета GRPACK. В противополож- ность версии, опубликованной в МГПК, в нее не включено ника- ких функций для заполнения замкнутых областей и для вычер- чивания окружностей, поскольку эти функции не нужны для программы D3D. Более важно, что данная версия воспринимает- ся компилятором Турбо Си (тогда как оригинальная версия была основана на компиляторе Латтис Си (Lattice С). Другим нововве- дением является то, что пакет GRPACK теперь дает более хоро- шие результаты на машинах с расширенным графическим адап- тером (EGA). Первоначальная версия могла различать графиче- ский адаптер Геркулес (HGA) с разрешением 720 * 348 и цветной графический адаптер (CGA) с разрешением 640 х 200 и выбирала последний вариант разрешающей способности, если использо- вался расширенный графический адаптер EGA. В новом вариан- те пакета GRPACK теперь выполняется автоматический выбор между тремя возможными адаптерами, а именно HGA, CGA (оба с только что упомянутой разрешающей способностью) и EGA с разрешением 640 х 350. Наконец, теперь существует возможность получения твердой копии на плоттере фирмы Хьюлетт-Паккард путем записи дан- ных в отдельный файл и считывания его впоследствии другой программой — PLOTHP, которая будет описана в параграфе 4.5. Поскольку этот файл является нормальным файлом в кодах ASCII, содержащим пользовательские координаты, его можно использовать также и для других целей. Например, программа PLOTHP может быть использована для отложенного вывода гра- фической информации на видеодисплей или матричный прин- тер. Другая возможность заключается в составлении специаль- ной программы, которая будет считывать этот файл и преобразо- вывать его в команды для каких-то других устройств вывода гра- фической информации. Кроме пакета GRPACK существует еще и альтернативный пакет GRPACK 1. Последний базируется на компиляторе Турбо Си, версия 1.5, который имеет встроенные графические средства, этот пакет обсудим несколько позже в этом же параграфе. Поскольку автор не предполагает обязательное знакомство
4.3. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ 205 читателя с его предыдущей книгой МГПК, то начнем сначала с пояснения, как могут быть использованы функции графики нижнего уровня. Будем различать вещественные экранные коор- динаты в дюймах, обозначаемые строчными буквами х и у, и це- лочисленными пикселными координатами, обозначаемыми про- писными буквами X и У. Начало вещественной координатной си- стемы располагается в нижнем левом углу экрана: 0 < х < xjnax 0 < у < yjnax Если не вызывалась функция setprdim, о чем будет речь ни- же, то xjnax= 10.0 у_тах= 7.0 (То есть мы предполагаем, что фактические размеры видеодисп- лея должны быть 10x7 дюймов, а поскольку это очень приблизи- тельно, то термин дюйм не должен восприниматься слишком буквально, если эти размеры относятся к видеодисплею). Начало (Х = 0, У= 0) пикселной системы координат распола- гается в верхнем левом углу экрана и мы имеем: 0 < X < Х_тах 0 < У < У_тах где максимальные значения для X и У зависят от типа адаптера, который будет использоваться: X max Y max CGA: 639 199 HGA: 719 347 EGA: 639 349 Подобно функциям, используемым в составляемых програм- мах, переменные xjnax, yjnax, X max, У max и некоторые другие, упоминаемые ниже, должны быть определены в модуле GRPACK.C и объявлены в заголовочном файле GRPACK.H. Этот файл рекомендуется включать в любую программу, кото- рая использует пакет GRPACK (или GRPACK1), вставляя в нее строку #include "grpack.h"
206 Глава 4. ПРОГРАММЫ D3D И PLOTHP В следующем списке перечислены все функции, доступные в пакете GRPACK (они также доступны и в пакете GRPACK1, но некоторые из них будут давать несколько отличный эффект, как увидим впоследствии). Аргументы х и у, записанные строчными буквами, имеют тип float, а те, что начинаются с прописных букв ХиУу имеют тип int. initgr () Инициируется графический пакет; видеодисплей переключается на графический режим. Должна применяться только в текстовом режиме. endgri) Ожидается нажатие клавиши. После этого виде- одисплей возвращается в текстовый режим. Не следует забывать включать обращение к этой функции (или к функции tojtext) перед заверше- нием работы программы. Должна применяться только в графическом режиме. tojtexti) Немедленный возврат в текстовый режим. Долж- на применяться только в графическом режиме. moveiXy у) Перемещение фиктивного пера в точку Ок, у), (где х и у неотрицательные вещественные коор- динаты, по величине не больше, чем xjnax и yjnaxy соответственно). Должна использоваться только в графическом режиме. drawiXy у) Вычерчивание отрезка прямой линии из текущей позиции пера в новую позицию пера Ос, у). Дол- жна применяться только в графическом режиме. imove(Xy У) Перемещение пера в точку (А", У) (где X и У неотрицательные целые пикселные координаты, по величине не больше, чем X max и У max соответственно). Должна применяться только в графическом режиме. idrawiXy У) Вычерчивание отрезка прямой линии из текущей позиции пера в новую позицию пера (Ху У). Дол- жна применяться только в графическом режиме. drawJine(X\, У1 ,Х2, У2) Вычерчивание отрезка прямой линии из точки (XI, У1) в точку (Х2, У2) (без изменения "текущей позиции пера"). Долж- на применяться только в графическом режиме.
4.3. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ 207 dot(X, Y) Подсвечивание точки с координатами (X, У). Применяется только в графическом режиме. clearpage() Стирание графической страницы (термин page — страница, включен в название функции вследст- вие того, что графический адаптер Геркулес HGA имеет две страницы, 0 и 1; здесь используется только страница 1). Должна применяться только в графическом режиме. text(str) Отображение текстовой строки str, начиная с текущей позиции пера (вероятно сформирован- ной как результат работы функций imove или move). "Текущая позиция пера" изменяется по- сле отрабатывания функции. Должна приме- няться только в графическом режиме. textXY(X> Y,str) Работает также, как функция text(str), за исключением начальной точки, которая в данном случае задается явно. Не изменяет "текущую по- зицию пера". Должна применяться только в гра- фическом режиме. printgr(XloyXhi, Yloy Yhi) Распечатывает (графическое) содержимое "области вывода" Xlo £ X < ХЫ, Ylo < У £ У/н, используя матричный принтер. См. также функцию setprtimi). Должна приме- няться только в графическом режиме. setprdimi) "Установка размеров для вывода на печать". Ес- ли обращение к этой функции встречается до об- ращения к функции initgr, то переменным xjnax и yjnax будут присвоены значения, соответству- ющие отношениям сторон на матричном принте- ре. В результате функция printgr будет осуществ- лять вывод информации на принтер с точным со- блюдением размеров как по горизонтальному, так и по вертикальному направлениям. Таким образом окружность (аппроксимируемая пра- вильным многоугольником) будет отпечатана в виде окружности, а не в виде эллипса. К сожале- нию, при этом на экране дисплея окружность по- явится в виде эллипса, поэтому эту функцию не
208 Глава 4. ПРОГРАММЫ D3D И PLOTHP рекомендуется включать в тех случаях, когда правильный результат требуется получить на эк- ране дисплея. Должна применяться только в гра- фическом режиме. Следующие четыре функции возвращают целочисленные значе- ния: IXОс) Целочисленная пикселная координата X, соот- ветствующая вещественной экранной координа- те х. Должна применяться только в графическом режиме. IY(y) Целочисленная пикселная координата У, соот- ветствующая вещественной экранной координа- те у. Должна применяться только в графическом режиме. pixlitiX, У) Возвращается значение 1, если пиксел с коорди- натами (X, Y) подсвечен, или 0, если он темный. Должна применяться только в графическом ре- жиме. iscolori) Возвращает код используемого графического адаптера: 2 = EGA (новое в этой версии пакета GRPACK) 1=С<7Л 0 = HGA -1 = (адаптер не пригоден для работы в графиче- ском режиме) Может применяться как в текстовом, так и в графическом режимах. В пакете GRPACK имеются три функции, которые по существу не являются графическими, хотя и имеют отношение к видеодис- плею. Они могут применяться только в текстовом режиме и их нельзя применять в графическом режиме: wrscr(row, column, str) Выводит текстовую строку str на экран, начиная с позиции row, column. Но- мера строк могут быть в диапазоне от 0 до 24, а номера столбцов — в диапазоне от 0 до 79. Пози- ция в верхнем левом углу имеет значения номе- ров строк и столбцов (0, 0). Должна применяться только в текстовом режиме.
4.3. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ 209 settxtcursor(row, colum) Вывод текстового курсора (мигающий символ подчеркивания) в позиции row, cursor. Должна применяться только в тексто- вом режиме. clearscri) Стирание экрана. Должна применяться только в текстовом режиме. Три последние функции wrscr, settxtcursor, clearscr не включа- лись в исходную версию пакета GRPACK и поэтому они не были описаны в предыдущей книге автора МГПК. В графическом режиме объекты могут быть удалены с экрана путем задания значения -1 для переменной drawmode, объяв- ленной в заголовочном файле GRPACK.H, нормально имеющей значение 1. При значении переменной drawmode = -1 функции drawу idrawy drawjine, dot из пакета GRPACK будут гасить соот- ветствующие пикселы. В качестве третьей возможности значе- ние переменной drawmode может быть установлено равным 0, что приведет к "переключению" пикселов: если пикселы были темными, то они будут подсвечены, а если пикселы были светлы- ми, то будут погашены при обращении к только что упомянутым четырем функциям. Как уже выше упоминалось, имеется заголовочный файл, ко- торый должен использоваться включением в программу строки #include "grpack.rT Автор настоятельно рекомендует применять ее в любой про- грамме, использующей пакет GRPACK (или GRPACK1). Текст этого заголовочного файла приведен ниже: /*GRPACK.H: */ /* Заголовочный файл для графических функций. V extern Int intextmode, X max, Y max, drawmode, adaptype; extern float x_max, y_max, horfact, vertfact; extern FILE *fplot; /*Этот файл применяется для отложенного вывода графической информации. Например, программа PLOTHP может прочитать этот файл и преобразовать его в сигналы управления видеоэкраном, матричным принтером или плоттером фирмы Хьюлетт-Паккард. Идентификатор FILE определен в заголовочном файле 'stdio.h', который вследствие этого должен быть обязательно подключен перед данным файлом. */
210 Глава 4. ПРОГРАММЫ D3D И PLOTHP #lfndef_HUGE_ #еггог Ошибка. Включите модель памяти Huge'. #endlf void initgr(void), void endgr(void); void totext(void); void move(float x, float y); void draw(float x, float y); void imove(intX, intY); void IdrawflntX, IntY); void draw_llne(int XI, int Y1, int X2, int Y2); void dot(intX, IntY); void clearpage(void); void text(char *str); void textXY(int X, Int Y, char *str); void prlntgr(lnt Xlo, int Xhi, int Ylo, int Yhi); void setprdim(vold); Int IX(float x); Int IY(float y); int pixlit(int X, Int Y); Int lscolor(vold); void wrscrflht line, int col, char *str); void settxtcursor(int line, int col); void clearscr(vold); Кроме переменных Xjnax, Yjnax, и drawmode, упомянутых выше, имеются еще две другие очень полезные глобальные пере- менные типа int: injtextmode - 1 — если система в текстовом режиме, в0 — если система в графическом режиме. adaptype значение, возвращаемое функцией iscolori) как , определено выше. Это значение присваивается переменной после вызова функции initgri), Кроме глобальных переменных xjnax и yjnax типа float имеют- ся еще две глобальных переменных этого же типа: horfact * количество пикселов на один дюйм в горизон- тальном направлении, vertfact - количество пикселов на один дюйм в вертикаль- ном направлении. Значения xjnax, yjnax, horfact, vertfact относятся к видеодисп- лею, если только перед обращением к функции initgri) не было обращения к функции setprdim (); в последнем случае они отно- сятся к матричному принтеру*
4.3. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ 211 Наконец, в пакете GRPACK определен указатель файла /plot, который также является глобальной переменной. Нормально по умолчанию он имеет значение NULL ("нуль") и тогда он игнори- руется. Если же ему будет приписано какое-либо другое значе- ние, обычно получаемое путем обращения к стандартной функ- ции /open, то при всех обращениях к графическим функциям move и draw кроме нормального эффекта их работы в виде выво- да изображения, в файл, описываемый указателем /plot, будут записываться текстовые строки. Каждая из таких текстовых строк будет содержать три числа: оба аргумента (х и у) из обра- щения к функциям move и draw и числа 0 или 1 как кода функ- ций move и draw соответственно. Таким образом, создаваемый нами чертеж в виде отрезков прямых линий не только появляет- ся на видеодисплее, но также становится доступным в машино- читаемой форме. В параграфе 4.5 будет показано, что это может оказаться очень полезным, особенно если в распоряжении чита- теля будет плоттер фирмы Hewlett-Packard. Исходные тексты функций, входящих в пакет GRPACK при- ведены ниже; многие из входящих в него функций были описаны в предыдущей книге автора МГПК. Напомним, что при обнару- жении элементов, которые нельзя использовать из-за примене- ния специфичного оборудования или программного обеспече- ния, то имеется альтернатива в виде пакета GRPACK 1 который будет описан после листингов пакета GRPACK. /* GRPACK: Графический пакет. Версия для Turbo С. */ /* . Должна использоваться модель памяти 'huge'. */ /* Вывод реализуется на: V /* — расширенный графический адаптер (EGA, 640 х 350); */ /* —цветной графический адаптер (CGA, 640 х 200); */ /* — монохромный графический адаптер Hercules (HGA, 720 * 348);*/ /* См. также книгу: */ /* Л. Аммерал, Машинная графика на персональных компьютеах */ /* —М.: "Сол Систем", 1992 » */ #include<stdio.h> #include<dos.h> #include<conio.h> #include <process.h> #include<stnng.h> #ifndef_HUGE__ #error Use huge mem. model. #endlf
212 Глава 4. ПРОГРАММЫ D3D И PLOTHP union REGS regs; Int in_textmode-1. X max, Y max, drawmode-1, adaptype; FILE*fplot-NULL; float xmax-10.0, y_max-7.0, horfact, vertfact; static intold_vid_state, X1. Y1, offset, Ystart[350]; /* 350 >- Ymax + 1 */ static long int startaddress; static char lastchar, gtable[12]-{53,45,46, 7,91,2,87,87,2, 3, 0, 0}, ttable[12]-{97,80,82, 15,25, 6, 25,25,2,13, 11,12}, zeros[128]; /* неявно инициализируется нулями*/ void to_text(void); void textXY(int X, int Y, char *str); int IX(float x) { return (intXx*horfact-K).5); } int IY(float y) { return Y_max-(intXy*vertfact-K).5); } static void error(char *str) /* Вывод сообщения и завершение.выполнения программы */ { if(!in_textmode)to_text(); prlntfC'XsXn", str); exit(1); } void clearpage(void) /* Стирание графического изображения с экрана */ { inti, n; unsigned segsrc, offsrc, segdest; n- (adaptype— 17128: adaptype —0 7 256:219); /* EGA: 219 x 128-28032 */ segsrc - FP_SEG(zeros); offsrc - FP_OFF(zeros); segdest - (adaptype — 2 ? 0xA000: 0xB800); for(l-0;i<n;i++) movedata(segsrc, offsrc. segdest, i« 7, 128); } int iscolor(void) /* Определение типа используемого адаптера */ { charchO, ch1, x; if (peekb(0x40,0x87)) return 2; /* EGA, см. Dr. Dobbs Journal, November 1987, p.34 */ int86(0x11, &regs, &regs); if ((regs.x.ax & 0x30)"!-0x30) return 1; /* CGA */ /* Цветной графический адаптер */
4.3. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ 213 outport(0x3BF, 3); /* Переключатель конфигурации */ chO- *(char *)0xB8000000; /* Попытка чтения chO из экранной памяти */ cM-chO^OxFF; /* Определение некоторого числа, отличного от chO */ *(char *)0хВ8000000 - ch 1; /* Попытка записать это число в экранную память */ x-*(char*)0xB8000000; /* Попытка прочитать последнее значение */ *(char *)0хВ8000000 - chO; /* Восстановление старого значения chO */ return (x — ch1 ?0:-1); /* Было ли прочитано записанное число? ' */ } static void setgrcon(int adaptype) /* Формирование графических констант */ { lntY.c1.c2.c3; startaddress - (adaptype — 2 ? OxAOOOOOOO: 0xB8000000); if (adaptype —2) { X_max-639;Y_max-349; for (Y-0; Y<-Y__max; Y++) Ystart[Y] - 80 * Y; return; } If (adaptype—-1) { X_max - 639; Y_max - 199; d-1;c2-80;c3-1; }else { X__max-719;Y_max-347; d-3;c2-90;c3-2; } for (Y-0; Y<-Y__max; Y++) Ystart[Y] - 0x2000*(Y&c 1) + c2*(Y»c3); static int grbrf un(void) /* Используется функцией ctrlbrk для определения, */ /* что делать при консольном прерывании */ { to_text(); return 0; } static void initcgaorega(int mode) /* Переключение в графический режим (CGA или EGA) */ { regs.h.ah-15; /* Запрос текущего состояния отображения */ int86(0x10, &regs, &regs);
214 Глава 4. ПРОГРАММЫ D3D И PLOTHP old_vid_state - regs.h.al; regs.h.ah-O; /* Установка графического режима */ regs.h.al-mode; /*mode-6: CGA, 640x200, черно/белый */ /* mode -16: EGA, 640x350, черно/белый */ int86(0x10, &regs, &regs); } static void initmongr(void) /* Переключение в графический режим (монохромный адаптер)*/ { static int firstcah-1; intl; outport(0x3B8,0x82); for(K);l<12;l++) { outport(0x3B4, i); outport(0x3B5, gtable[i]); } if (firstcall){ firstcall-0; clearpage(); } outport(0x3B8,0x8A); } void initgr(vold) /* Инициализация графического пакета */ { if(!ln_textmode) еггог( "Обращение к функции initgr в графическом режиме"); adaptype - iscolor(); if (adaptype < 0) еггог( "Неверный дисплейный адаптер"); ctrlbrk(grbrfun); /* Установка прерывания для Turbo С */ if (adaptype —2) initcgaorega(16);else /* EGA */ if (adaptype—1) lnitcgaorega(6); else /* CGA */ /* adaptype — 0*/ lnitmongr(); /* HGA */ setgrcon(adaptype); in_textmode-0; horfact - X max/xmax; vertfact - Y max/ymax; } static void endcolgrfvold) /* Возврат в текстовый режим */ /* (для цветного графического адаптера или EGA): */ { regs.h.ah - 0; regs.h.al - old_vld_state; Int86(0x10, &regs, &regs); }
4.3. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ 215 static void endmongr(void) /* Возврат в текстовый режим */ /* (для монохромного графического адаптера): */ { inti; char*source; unsigned segsrc, offsrc; outport(0x3B8.0); for(M);l<12;l++) { outport(0x3B4, i); outport(0x3B5, ttable[i]); } source - "\40\7\40\7\40\7\40\7\40\7\40\7\40\7\40\7H; segsrc - FP_SEG(source); offsrc- FPOFF(source); for(M;i<256;i++) movedata(segsrc, offsrc, OxBOOO, I« 4, 16); outport(0x3B8,0x08); } static int txtbrfun(void) { return 0; void totext(void) /* Возврат в текстовый режим */ { If(injextmode) error( "функции endgr или totext вызваны в текстовом режиме"); if(adaptype)endcolgr(); /*CGAhahEGA */ elseendmongr(); /* HGA */ intextmode- i; ctrlbrk(txtbrfun); /* Восстановление обработки прерываний по умолчанию V } void endgr(vold) /* Ожидание нажатия любой клавиши, после чего */ /* возврат в текстовый режим */ { getch(); to_text(); } void dot(lntX, int Y) /* Подсветка или гашение пиксела */ { int pattern; offset-Vstart[Y] + (X»3); lastchar- *(char *Xstartaddress + offset); У* Турбо Си */ pattern - 0x80 »(X &7); if (drawmode — 1) lastchar I - pattern; els«
216 Глава 4. ПРОГРАММЫ D3D И PLOTHP if (drawmode — -1) lastchar &- (^pattern); else lastchar ^-pattern; *(char *Xstartaddress + offset)- lastchar; } static void checkbreak(vold) { charch; If (kbhlt()){ ch-getch(); kbhlt(); ungetch(ch);} } void draw_llne(lnt X1, Int Y1. Int X2. Int Y2) /* Вычерчивание отрезка прямой из (Х1, Y1) в (Х2, Y2); */ /*Х1, Y1.X2, Y2 —впикселных координатах */ {• Int X, Y, T, E, dX, dY, denom, Xlnc-1, Ylnc - 1, vertlonger-O, aux; checkbreak(); /* To make DOS check for console break */ /* Установка в ДОС консольного прерывания */ if(intextmode) error( uHe в графическом режиме (нужен вызов inltgr)"); dX-X2-X1;dY-Y2-Y1; if(dX<0){Xinc—1;dX— dX;} lf(dY<0){Ylnc—1;dY—dY;} lf(dY>dX) { vertlonger -1; aux - dX; dX - dY; dY - aux; } denom-dX« 1; T-dY«1; E—dX;X-X1; Y-Y1; if (vertlonger) while (dX—>-0) { dot(X.Y); lf((E4-T)>0) { X +- Xinc; E — denom; } Y 4- Ylnc; }else while (dX—>-0) { dot(X,Y); if((E-H-T)>0) { Y-H-Ylnc;E —denom; } Х-н-Xinc; } if(tlrawmode —0)dot(X1,Y1);
4.3. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ 217 static void fatal(void) /* Вычерчивание диагонали на весь экран, затем ожидание */ /* нажатия на любую клавишу для окончательного перехода */ /* в текстовый режим. */ { draw_line(0, Y max, X max, 0); endgr(); } static void check(int X, int Y) /* Если точка (X, Y) лежит вне пределов экрана, то вызов */ /* функции fatal, отображение неверных координат и останов */ { if(X<0 I I X>X_max II Y<0 II Y>Y__max) { fatalQ; printf ("Точка вне экрана (X и Y в пикселных координатах):\п"); printf ("X-%d Y-%d\n",X,Y); printf ("x-%10.3f y-%10.3f\n", X/horfact, (Y_max-Y)/vertfact); pnntf("X_max-%d Y_max-%d x_max- %f y_max- %f\n", X max, Y max, xmax, ymax); printf("horfact- °/£f vertfact- % f\n", horfact, vertfact); exit(1); } } void move(float x, float y) /* Перенос текущей точки в (х, у); */ /* х и у в экранных координатах */ { intXX.YY; XX - IX(x); YY -1 Y(y); check(XX, YY); if (fplot l-NULL &&(XX !«X1 I I YY !-Y1)) fprintf(fplot, "%6.3f %6.3f 0\n", x, y); X1-XX;Y1=YY; } void draw(float x, float y) /* Вычерчивание отрезка прямой линии */ /* из текущей точки в точку (х, у) */ { intX2,Y2; Х2 - IX(x); Y2 -1Y(y); check(X2, Y2); draw_line(X1,Y1,X2,Y2); if(fplotl-NULL) fprintf(fplot, "%6.3f %6.3f 1\n", x, y); X1-X2;Y1-Y2; }
218 Глава 4. ПРОГРАММЫ D3D И PLOTHP Int pixlit(int X, int V) /* Запрос, не подсвечен ли пиксел (X, Y) */ { Int pattern; offset- Ystart[Y] + (X»3); pattern-0x80 »(X&7); lastchar - *(char *Xstartaddress + offset); /* Turbo С */ /* Турбо Си */ return ((lastchar & pattern) !-0); } static void prchar(char ch) /* Посылка байта ch на параллельный порт принтера */ { regs.x.dx-O; /* Выбор принтера v */ regs.h.ah-O; /* Посылка байта из AL на принтер */ regs.h.al-ch /* Байт, посылаемый на принтер */ Int86(0x17, &regs, &regs); } void prlntgrtjnt Xlo, Int Xhl, Int Ylo, Int Yhl) /* Вывод содержимого прямоугольника на матричный принтер */ { Intnl. n2, ncols, I, X, Y, val, Xhl1; charllne[720]; /*720>-X__max */ regs.x.dx-0; /*LPT1 */ regs.h.ah - 2; /* Режим 2: Вызов состояния принтера */ Int86(0x17, &regs, &regs); /* Обслуживание принтера */ If ((regs.h.ah & 8) —8) л /* Состояние принтера в АН */ { If (ln_textmode) prlntf( "В текстовом режиме"); else textXY(0, Y max- 15, "Принтер не готов"); return; } prchar(27); prchar(' Г); /* Расстояние между строками 7/72 дюйма */ for(l-Ylo;K-Yhl;l4-7) { checkbreakQ; /* Установка в ДОС проверки консольного прерывания */ ХЫ1-Х1о-1; for(X-Xlo;X<-Xhl;X4+) { val-0; for(Y4;Y<H7;Y++) { val«- 1; val I - (Y>Yhl ? 0: plxlltpC, Y)); } line[X]-val; If (val) Xhl 1-Х; }
4.3. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ 219 ncols-Xhl1-Xlo+1; If(ncols) { n1-ncois%256; n2-ncols/256; prchar(27); prchar('L'); prchar(n1); prchar(n2); for(X-Xlo; X<-Xhi1; X++) prchar(line[X]); } prcharf\n'); } prchar(27); prchar('@')', } void setprdlm(void) /* Эта функция устанавливает такие значения x_max и */ /* ymax, чтобы графическое изображение было напечатано */ /* с точным соблюдением размеров как по горизонтали, */ /* так и по вертикали */ { extern float xmax, углах; extern Int X max, Y^max; setgrcon(iscolor()); x_max - (X_max + 1)/120.0; y_max-(Y__max +1)/72.0; } #defineNASCII256 charchllst[NASCIII11}- { {0}, {0}, {0}, {0}. {0}, {0}, {0}, {0}. {0}, {0}, {0}, {0}, {0}, {0}, {0}. {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}. {0}, {0}, {0}. {0}, {0}, {0}, {0}. {0}, {0}, /* */ {0}, /*!*/ {0x10,0x38,0x38,0x38,0x10,0x10.0x10, 0,0x10}, /*"*/ {0x48,0x48, 0x48}, /*#*/ {0x24,0x24,0x64, OxFE, 0x44, OxFE, 0x4C, 0x48, 0x48}. /*$*/ {0x10,0x7C, OxDO, OxDO, 0x7C, 0x16,0x16,0x7C, 0x10}, /*%*/ {0xC2,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x86}, /*&*/ {0x30,0x48,0x48,0x30,0x50.0x92,0x8A, 0x8C, 0x72}, /*'*/ {0x18,0x18,0x18,0x10}, /*(*/ {0x08,0x10,0x20,0x20, 0x20,0x20,0x20,0x10, 0x08}, /*)*/ {0x40,0x20,0x10,0x10,0x10,0x10,0x10.0x20, 0x40}, /***/ { 0,0x82,0x44,0x28, OxFE, 0x28,0x44,0x82}, /*+*/ { 0,0x10,0x10,0x10, OxFE, 0x10,0x10,0x10}, /*.*/ {0,0,0,0,0,0, 0,0x30,0x30,0x10.0x20}, /*-*/ {0,0,0,0, OxFE}, /*.*/ {0,0,0,0,0,0, 0,0x30,0x30}, /*/*/ { 0,0x02,0x04,0x08,0x10,0x20,0x40,0x80}, /*0*/ {0x38,0x6C, 0x44,0x44,0x44,0x44,0x44,0x6C. 0x38}, /*1*/ {0x10,0x30.0x50,0x10,0x10,0x10,0x10.0x10,0x38}. /*2*/ {0x7C, 0xC6,0x02,0x06, OxOC, 0x18,0x30,0x60, OxFE}.
220 Глава 4. ПРОГРАММЫ D3D И PLOTHP /*3*/ {Ох7С, ОхСб, 0x02,0x06, ОхОС, 0x06,0x02, ОхСб, 0х7С}, /М*/ {0x04, ОхОС, 0х1С, 0x34, 0x64,0хС4, OxFE, 0x04, 0x04}, /*5*/ {OxFE, 0x80,0x80, OxFC, 0x06,0x02.0x02, ОхСб, 0х7С}, /*6*/ {0х7С, ОхСб, 0x80, OxFC. ОхСб, 0x82,0x82, ОхСб, 0х7С}, /*7*/ {OxFE, 0x02,0x06, ОхОС, 0x18,0x30,0x60, ОхСО, 0x80}, /*8*/ {Ох7С, ОхСб, 0x82, ОхСб, 0х7С. ОхСб. 0x82. ОхСб, 0х7С}. /*9*/ {0х7С, ОхСб. 0x82.0хС2, 0х7Е, 0x02,0x02,0x06, Ох7С}, /*:*/ { 0.0x30,0x30.0.0.0.0.0x30,0x30}, /*;*/ {0,0.0x30.0x30.0,0. 0,0x30.0x30.0x10.0x20}. /*<*/ {0x04.0x08,0x10,0x20. 0x40.0x20.0x10.0x08. 0x04}, /*-*/ {0,0,0.0, OxFC, О, О, OxFC}. /*>*/ {0x40,0x20,0x10,0x08, 0x04,0x08,0x10,0x20. 0x40}. /*?*/ {0x78. ОхСС, 0x84. ОхОС. 0x18,0x10,0x10,0, 0x10}, /*@V {Ох7С, ОхСб, 0х8Е. 0x92, 0x92,0x92,0х8С, ОхСО, Ох7С}, /*А*/ {0x10,0x38, ОхбС, ОхСб, 0x82,0x82, OxFE, 0x82, 0x82}, /*BV {OxFC, 0x86,0x82,0x86, OxFC, 0x86,0x82,0x86. OxFC}. /*C*/ {0x7C. ОхСб. 0x80.0x80. 0x80.0x80.0x80. ОхСб, 0х7С}, /*DV {OxFC, 0x86,0x82,0x82, 0x82,0x82.0x82.0x86. OxFC}. /*EV {OxFE, 0x80,0x80.0x80, 0xF8,0x80.0x80.0x80, OxFE}, /*FV {OxFE, 0x80,0x80,0x80, OxFC, 0x80,0x80.0x80, 0x80}, /*G*/ {0x7C, ОхСб, 0x82,0x80, 0x80,0x8E. 0x82. ОхСб. 0x7C}. /*H*/ {0x82.0x82.0x82.0x82. OxFE. 0x82.0x82,0x82, 0x82}, /*l*/ {0x38,0x10,0x10,0x10,0x10,0x10.0x10.0x10. 0x38}. /*JV {0x02.0x02.0x02.0x02.0x02,0x02.0x02, ОхСб, Ох7С}, /*KV {0x86,0x8C, 0x98, OxBO, OxEO, OxBO, 0x98,0x8C. 0x86}, /*LV {0x80,0x80,0x80,0x80.0x80.0x80.0x80.0x80, OxFE}. /*MV {0x82. ОхСб. OxEE. OxBA. 0x92.0x82.0x82.0x82. 0x82}. /*NV {0x82.0xC2, OxE2, OxA2, 0xB2.0x9A, 0x8E, 0x86, 0x82}, /*0*/ {0x7C, ОхСб, 0x82,0x82, 0x82,0x82,0x82, ОхСб, 0х7С}, /*P*/ {OxFC, 0x86,0x82,0x86, OxFC, 0x80,0x80,0x80, 0x80}, /*QV {0x7C, ОхСб, 0x82.0x82, 0x82,0x82.0x92,0xD6,0x7C, 0x08,0x08}, /*RV {OxFC, 0x86,0x82,0x86, OxFC, 0x90,0x98,0x8C, 0x86}. /*SV {0x7C, ОхСб, 0x80, OxCO. Ox7C, 0x06,0x02, ОхСб, 0x7C}. /*TV {OxFE, 0x10,0x10,0x10, 0x10,0x10,0x10,0x10,0x10}, /*UV {0x82,0x82,0x82,0x82,0x82,0x82.0x82. ОхСб. 0х7С}, /*V*/ {0x82, 0x82,0x82, ОхСб, 0x44, ОхбС, 0x28,0x38, 0x10}, /*W*/ {0x82,0x82, 0x82,0x92, 0x92, OxBA, OxEE, ОхбС, 0x44}, /*X*/ {ОхСб, 0x44, ОхбС, 0x28, 0x38,0x28, ОхбС, 0x44, ОхСб}. /*Y*/ {0x82,0x82, ОхСб, ОхбС, 0x38,0x10,0x10,0x10, 0x10}, /*Z*/ {OxFE, 0x04, ОхОС, 0x18, 0x10,0x30,0x60,0x40, OxFE}, /*[*/ {0х7С, 0x40,0x40,0x40, 0x40,0x40,0x40,0x40, 0х7С}, /*W { 0,0x80,0x40,0x20, 0x10,0x08,0x04,0x02}, /*]*/ {0x78,0x08,0x08,0x08,0x08,0x08,0x08,0x08, 0x78}, /*/ч*/ {0х10| 0x28,0x44, 0x82}, /*_V {О, О, О, О, 0,0,0,0. OxFE}, /*'*/ {0x40.0x20.0x10. 0x08}, /*а*/ {0,0, 0,0х7С,0х06,0х7Е,0хС2.0хС2.0х7Е}, /*Ь*/ {0x80,0x80,0x80, OxFC, 0x86,0x82.0x82,0x86. OxFC},
4.3. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ N Ш /*с*/ {0,0, 0,0х7С,0хС6,0х80,0х80,0хС6,0х7С}, /*d*/ {0x04,6x04,0x04,0х7С, 0хС4,0x84,0x84,0хС4, 0х7С}, /*е*/ {0,0, 0,0x7C,0xC6.0xFE.0x80,0xC0,0x7C}. /*f*/ {0x1C, 0x30,0x20, OxFC, 0x20,0x20,0x20,0x20, 0x20}, /*g*/ {0,0, 0.0х7А, ОхСЕ, 0x82.0x82,0хС2,0х7Е, 0x06,0х7С}. /*h*/ {0x80,0x80,0x80, OxFC, ОхСб, 0x82,0x82,0x82. 0x82}, /*i*/ { 0,0x30, 0,0x30,0x10,0x10,0x10,0x10,0x38}. /*J*/ { О.ОхОС, 0, ОхОС. 0x04,0x04,0x04,0x04,0x04, OxCC. 0x78}, /*к*/ {0x40.0x40,0x40,0x46. 0х4С, 0x78.0x58,0х4С, 0x46}. /*!*/ {0x30.0x10,0x10.0x10. 0x10.0x10,0x10.0x10, 0x38}. /*т*/ {0,0. О, ОхЕС, 0x92, 0x92,0x92,0x92,0x92}, /*п*/ {0,0. О, ОхВС, ОхСб, 0x82,0x82,0x82,0x82}, /*о*/ {0,0. 0.0х7С, ОхСб, 0x82,0x82, ОхСб, Ох7С}, /*р*/ {О, О, О, OxFC, 0x86, 0x82,0x82,0x86, OxFC, 0x80,0x80}, /*q*/ {0,0, 0,0х7С, 0хС4, 0x84,0x84,0хС4, Ох7С, 0x04,0x06}, /*г*/ {0,0, 0, ОхВСОхЕб, 0x80,0x80,0x80,0x80}, /*s*/ {0,0, 0,0х7С,0хС0,0х7С,0х06,0х06,0х7С}, /*t*/ { 0, 0x40,0x40, OxFO, 0x40,0x40,0x40, 0x66, ОхЗС}, /*и*/ {0,0, 0,0x84,0x84, 0x84,0x84,0хС4. Ох7Е}, 1*ч*1 {0,0, 0,0x82,0x82. ОхСб, ОхбС, 0x38,0x10}, 1*4**1 {0,0, 0,0x82,0x82, 0x92, ОхВА.ОхЕЕ, 0x44}, /*х*/ {0,0, 0, ОхСб, ОхбС, 0x28,0x38, ОхбС, ОхСб}, /*у*/ {0.0. 0,0x82,0x82, 0x82,0x82. ОхСб, Ох7Е, 0x06,0х7С}, 1*г*1 {0,0, 0, OxFE, ОхОС, Ох 18,0x30,0x60, OxFE}, /*{*/ {0x10,0x20,0x20.0x20, 0x40, Ох?0.0x20,0x20, 0x10}, /* I */ {0x10.0x10.0x10.0x10, 0x10,0x10,0x10,0x10, 0x10}, /*}*/ {0x20,0x10,0x10,0x10, 0x08,0x10,0x10,0x10, 0x20}, 1*~*1 {0,0, 0,0x60,0x92, ОхОС}. {0}, /*А*/ {ОхЗЕ, 0x42,0x82,0x82, 0x82, OxFE, 0x82,0x82, 0x82}, 1*Ъ*1 {OxFC, 0x80,0x80, OxFC, 0x82,0x82,0x82,0x82, OxFC}, /*В*/ {OxFC, 0x82,0x82,0x84, OxFC, 0x82,0x82,0x82, OxFC}. /*Г*/ {OxFC, 0x80,0x80,0x80,0x80,0x80,0x80,0x80, 0x80}. /*Д*/ {Ох1С, 0x24,0x44,0x44,0x44,0x44, 0x44,0x44, OxFE, 0x82}, /*EV {OxFC, 0x80,0x80.0x80,0xF8,0x80.0x80,0x80, OxFC}, 7*Ж*/ {0x92,0x92,0x54,0x38, 0x38,0x54,0x92,0x92, 0x92}, 1*3*/ {ОхЗС, 0x42,0x02,0x04, ОхЗС, 0x02,0x02.0x82. Ox7C}. ЛИ*/ {0x82,0x82,0x86,0x8E, 0x9A, 0xB2,0xE2,0xC2, 0x82}, /*Й*/ {OxBA, 0x82,0x86, 0x8E, 0x9A, 0xB2,0xE2, OxC2. 0x82}, /*K*/ {0x84, 0x84,0x84,0x88, OxFO, 0x88,0x84,0x82, 0x82}. /*П*/ {0x1 E, 0x22,0x42,0x42, 0x42,0x42,0x42,0xC2, OxC?}, /*M*/ {0x82, ОхСб, OxEE, OxBA, 0x92.0x82,0x82,0x82, 0x82}. /*HV {0x82.0x82.0x82.0x82, OxFE, 0x82, 0x82,0x82, 0x82}. 1*0*/ {0x7C, 0x82,0x82,0x82; 0x82,0x82,0x82,0x82, 0x7C}, /*П*/ {OxFE, 0x82, 0x82,0x82, 0x82, 0x82, 0x82,0x82, 0x82}, /*?*/ {OxFC, 0x82,0x82,0x82, 0x82, OxFC, 0x80,0x80. 0x80}, /*C*/ {0x7C. 0x82,0x80,0x80, 0x80,0x80,0x80,0x82, 0x7C}, 1*1*1 {OxFE. 0x10,0x10,0x10,0x10,0x10,0x10,0x10, 0x10}, /*У*/ {0x82,0x82,0x82, 0x82, 0x82, 0x7E, 0x02,0x02, 0x7C},
222 Глава 4. ПРОГРАММЫ D3D И РЬОТНР /*Ф*/ {0x10,0х7С, 0x92,0x92,0x92.0x92,0х7С, 0x10,0x10}, /*Х*/ {0x82. ОхСб, ОхбС, 0x38,0x10,0x28, ОхбС, ОхСб, 0x82}, /*Ц*/ {0x84,0x84,0x84,0x84.0x84,0x84,0x84,0x84,0х7Е, 0x06}. /*Ч*/ {0x82,0x82,0x82,0x82.0x82,0х7Е, 0x02,0x02, 0x02}, /*Ш*/ {0x92.0x92.0x92,0x92.0x92.0x92.0x92.0x92, 0х7Е}, /*Щ*/ {0x92.0x92.0x92.0x92,0x92.0x92. 0x92,0x92, OxFE. 0x06}, /*Ь*/ {ОхСО, ОхСО, 0x40, Ох7С, 0x42.0x42.0x42,0x42.0х7С}. /*Ы*/ {0x82,0x82,0x82, OxF2. 0x8A, 0x8A, 0x8A, 0x8A, 0xF2}, /*Ь*/ {0x80,0x80,0x80. OxFC, 0x82,0x82,0x82,0x82, OxFC}, /*Э*/ {Ох7.С, 0x82,0x02,0x02, ОхЗЕ, 0x02,0x02,0x82, 0х7С}, /*Ю*/ {0х9С, 0хА2. ОхА2,0хА2, ОхА2, ОхЕ2, ОхА2,0хА2, 0х9С}, ' /*Я*/ {ОхЗЕ, 0x42.0x42.0x42.0x42. ОхЗЕ, 0x22,0x42,0x82}, /*а*/ {0,0, 0,0x78,0x04, Ох7С, 0x84,0x84,0х7А}, /*6*/ {0,0,0x78,0x80.0xF8,0x84,0x84,0x84.0x78}, /*в*/ {0.0, 0,0xF8,0x84,0xF8,0x84,0x84, OxF8}, /*г*/ {0,0, 0, OxFC, 0x80,0x80,0x80.0x80,0x80}. /*д*/ {0,0,0x78,0x04,0х7С, 0x84,0x84,0x84,0x78}. /*е*/ {0,0, 0,0x78,0x84,0x84,0xF8.0x80,0х7С}, /*ж*/ {0,0, 0,0x92,0x54,0x38,0x38.0x54,0x92}. /*з*/ {0,0, 0,0x78,0x84,0x18.0x04,0x84,0x78}, /*и*/ {0,0, 0,0x84,0х8С, 0x94,0хА4.0хС4.0x84}. /*й*/ {0.0.0x30,0x84,0х8С. 0x94.0хА4. ОхС4.0x84}. /*к*/ {0.0. 0,0x84,0x88, OxFO, 0x88,0x84,0x82}, /*л*/ {0,0, 0. ОхЗС. 0x44. 0x44.0x44.0x44.0x84}. /*м*/ {О, О. 0,0x82, ОхСб, ОхАА, 0x92,0x82,0x82}, /*н*/ {0,0. 0.0x84.0x84.0x84. OxFC, 0x84.0x84}. /*о*/ {0.0. 0,0x78,0x84,0x84,0x84,0x84,0x78}, /*п*/ {О, О, О, OxFC. 0x84,0x84,0x84,0x84,0x84}, {0}, {0}, {0}, {0}. {0}, {0}. {0}. {0}. {0}. {0}. {0}, {0}, {0}. {0}. {0}. {0}. {0}. {0}, {0}, {0}. {0}, {0}, {0}. {0}. {0}. {0}, {0}, {0}, {0}. {0}. {0}, {0}. {0}. {0}. {0}, {0}. {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}. {0}, {0}, {0}. {0}. /*р*/ {0,0, 0,0xF8,0x84,0x84,0x84.0x84,0xF8, 0x80,0x80}. /*с*/ {0,0. 0,0x78,0x84,0x80,0x80,0x84,0x78}, /*т*/ {0,0. 0. OxFE, 0x10,0x10,0x10,0x10,0x10}. /*y*/ {0.0. 0,0x84,0x84,0x84,0x84,0x84. Ox7C, 0x04,0x78}, /*ф*/ {0.0,0x10, Ox7C. 0x92.0x92.0x92,0x92, 0x7C. 0x10,0x10}, /*x*/ {0,0. 0- 0x84,0x48.0x30.0x30,0x48.0x84}. /*ц*/ {0,0, 0,0x84,0x84,0x84,0x84,0x84. Ox7E. 0x06}. /*ч*/ {0.0. 0,0x84.0x84,0x84. Ox7C. 0x04.0x04}. /*ui*/ {0.0. 0.0x92.0x92.0x92.0x92.0x92.0x7E}, /*щ*/ {0.0. 0.0x92.0x92. 0x92.0x92.0x92. Ox7E. 0x03}, /*ъ*/ {0. 0. 0. ОхСО. 0x40, 0x78,0x44,0x44,0x78}, /*ы*/ {0.0. 0,0x82.0x82.0xF2,0x8A,0x8A,0xF2}, /*ь*/ {0.0, 0.0x80.0x80. 0xF8.0x84.0x84.0xF8}. /*э*/ {0.0, 0,0x78.0x84. 0x04. ОхЗС. 0x84.0x78}.
4.3. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ 223 /*ю*/ {0,0, 0,0x98,0хА4,0хА4,0хЕ4,0хА4,0x98}, /*я*/ {0,0, 0, ОхЗС, 0x44,0x44, ОхЗС, 0x44.0x84}, {0}, {0}, {0}, {0}, {0}. {0}, {0}, {0}, {0}.{0},{0}.{0Н0}.{0}.{0}.{0}}; void textXY(int X, Int Y, char *str) /* Отображение текстовой строки str, */ /* начиная с заданной точки (X, Y). */ { char*p; int offset, i, j, len, hpos, vpos; len-strlen(str); hpos-X»3; check(8*(hpos+len)-1. Y+10); for(W;Klen;l++) { p-chllst[str[iD; forQ-0;J<11;J++) { vpos-Y+J; offset- Ystartfvpos] + hpos +1; *(char *Xstartaddress + offset) - p[J]; . } } } void text(char *str) /* Отображение текстовой строки str, */ /* начиная с текущей точки (Х1, Y1). */ { textXY(X1,Y1,str); X1^-strlen(str)*8; check(X1,Y1); void lmove(lnt X, Int Y) /* Теперь (X, Y) — новая текущая точка */ { check(X.Y);X1-X;Y1-Y; } ' void ldraw(lnt X, Int Y) /* Вычерчивание отрезка прямой линии */ /* из текущей точки в точку (X, Y) */ { check(X.Y); drawJlne(X1,Y1,X,Y); X1-X;Y1-Y; }
224 Глава 4. ПРОГРАММЫ D3D И PLOTHP ♦define BOOO(char *)OxBOOOOOOO ♦define B800(char *)0xB8000000 void wrscr(int row, Int col, char *str) /* В текстовом режиме */ /* Запись символов в память видеодисплея при работе в */ /* текстовом режиме */ /* row-0,1 24; col-0,1 79 */ { static char *displayptr; static int first—1; Int i. offset; char *p; if (first) { displayptr-(iscolor() ? B800: B000); first-0; } offset- row* 16f>col*2; p - di splay ptr+off set; for(M); strfl]; I++) p[i« 1]- strfi]; } void clearscr(void) /* Должна использоваться в текстовом режиме. */_ /* Не следует путать с функцией clearpage, которая */ /* применяется в графическом режиме */ { static unsigned segdst; static first—1; int I; char *source; unsigned segsrc, offsrc; if (first) { segdst - (iscolor() ? 0xB800: OxBOOO); first-O; } source- И\40\7\40\7\40\7\40\7\40\7\40\7\40\7\40\7И; segsrc - FPSEG(source); offsrc - FPOFF(source); for(i-0;i<256;i-bb) movedata(segsrc, offsrc, segdst, i«4, 16); void settxtcursor(int row, int col) /* Установка курсора в текстовом режиме */ { regs.h.dh-row; regs.h.dl-col; regs.h.ah-2; regs.h.bh-0; int86(0x10, &regs, &regs);
4.3. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ 225 Напомним, что в языке Си ключевое слово static, поставлен- ное перед описанием функции означает, что рассматриваемая функция является локальной в том модуле, в котором она опре- делена. Такие функции предназначены только для внутреннего использования и они недоступны для пользователя. Все функции пакета GRPACK, не обсуждавшиеся ранее, имеют такое ключе- вое слово static и таким образом их легко отличить от других функций, которые могут вызываться из пользовательских про- грамм. Если, возможно совсем не преднамеренно, точно такое же имя будет определено пользователем как имя собственной функ- ции, то никаких неприятностей не возникнет, поскольку имена функций с префиксом 'static' редактору связей не сообщаются. Как раз перед окончанием рукописи данной книги автор по- лучил новую версию 1.5 компилятора Турбо Си от фирмы Borland International (вероятно, как весьма великодушный отк- лик на сообщение автора о двух не очень существенных ошибках в компиляторе). Эта новая версия предоставляет прекрасные графические возможности базового уровня, которые можно использовать вместо пакета GRPACK. Хотя пакет GRPACK работал вполне удовлетворительно на доступных автору компь- ютерах, стало ясно, что совершенно бесполезно конкурировать с фирмой Borland в проверке работы графических подпрограмм на всех типах существующих и будущих машин. Но не рационально и полностью принять методику работы, предлагаемую этой фир- мой. Поэтому была рассмотрена возможность замены подпрог- рамм из пакета GRPACK и радикального перехода на новые гра- фические функции Турбо Си. Пользователи более ранних версий компилятора Турбо Си рано или поздно заменят свои компиляторы, поэтому ориента- ция на версию 1.5 и более старшие не вызовет серьезных возра- жений. Однако, по мнению автора, некоторые аспекты Турбо Си не все пользователи могут принять безоговорочно. Из-за общего характера графические подпрограммы Турбо Си занимают зна- чительно больше памяти, а некоторые из них работают медлен- нее, чем аналогичные подпрограммы из пакета GRPACK, кото- рый можно назвать небольшим. Но если небольшой пакет делает вполне удовлетворительно все, что нужно, то почему бы не пред- почесть его более расширенному пакету. В пакете GRPACK все действия выполняются на основе функций очень низкого уровня, 8 — 273
226 Глава 4. ПРОГРАММЫ D3D И PLOTHP таких как int86 (для программного прерывания) и out port (для обращения к портам ввода/вывода), которые являются хорошо известным средством по крайней мере для всех, знакомых с про- граммированием персональных компьютеров. Следовательно, эти средства доступны для пользователей и других компиляторов языка Си. Последнее обстоятельство очень важно, поскольку в обращении существуют много других хороших компиляторов и совершенно не очевидно, что все пользователи персональных компьютеров, программирующих на языке Си, перейдут на ком- пилятор Турбо Си. Короче, графические подпрограммы пакета GRPACK вполне открыты, тогда как аналогичные подпрограм- мы в Турбо Си должны применяться как черные ящики. Возможно наиболее важной причиной не отказываться кате- горически от пакета GRPACK является то, что он позволяет по- лучать выполняемые программы (файлы типа ххх.ЕХЕ), кото- рые могут работать с различными графическими адаптерами без необходимости процедуры "установки" ("инсталляции") или потребности в наборе "драйверных файлов". В противополож- ность этому новая функция initgraph в Турбо Си (используемая для переключения из текстового режима в графический режим) нормально загружает драйвер для установленного графического адаптера во время выполнения программы. Это означает, что универсальная выполняемая программа, предназначенная для работы на любой машине, независимо от типа адаптера, должна сопровождаться различными драйверными файлами. Вместо этого можно использовать утилиту (BGIOBJ) для преобразова- ния драйверных файлов в объектные файлы и затем связывать их вместе с нашими программами, но в этом случае придется уста- навливать связи ко всем драйверам, которые могут потребовать- ся. Все это достаточно сложно и с учетом некоторых других воз- ражений, часть которых уже была упомянута, автор полагает, что найдутся читатели, которые согласятся с его предложением по-прежнему использовать пакет GRPACK до тех пор, пока он работает достаточно удовлетворительно. Как уже было сказано в начале этого параграфа, графические функции базового уровня играют очень существенную роль, по- скольку без них наши графические приложения высокого уровня просто не будут работать. Поэтому совершенно неправомерно считать напрасной затрату времени на анализ другого решения
4.3. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ 227 такой весьма критической проблемы, как отображение линий и символов на экране нашего компьютера. Цепь всегда настолько прочна, насколько прочно ее самое слабое звено, и если окажет- ся, что этим звеном является пакет GRPACK (что вполне воз- можно из-за его приборно-зависимых аспектов) тогда было бы неплохо его заменить. Кроме того, всегда найдутся читатели этой книги, которые с восторгом относятся к графическим функ- циям Турбо Си версий 1.5 и выше и которые тем не менее хотят использовать их в связи с пакетом D3D. Этот набог) функций можно было бы даже назвать "прекрасным", несмотря на неко- торые недостатки по сравнению с пакетом GRPACK. Из положи- тельных характеристик графических функций Турбо Си можно упомянуть наличие различных типов и стилей шрифтов, воз- можность манипуляций с областью вывода, заполнение замкну- тых областей различными шаблонами, использование цвета, отображение текста внутри окна. Поэтому было бы совсем нера- зумно не использовать в этой книге новые графические средства компилятора Турбо Си. Поскольку модули D3D и HLPFUN сравнительно большие и сложные, было бы не очень красиво иметь два их варианта, один для пакета GRPACK, другой для Турбо Си версии 1.5. Вместо этого выразим функции пакета в терминах новых функций Турбо Си. Таким образом мы получим новый пакет GRPACK 1, почти аналогичный пакету GRPACK с точки зрения пользовате- ля , но который обращается к новым функциям Турбо Си. Он может быть скомпилирован совершенно отдельно для получения модуля GRPACK1.0BJ, заменяющего модуль GRPACK.OBJ. Остальные модули (D3D, HLPFUN, TRAFO), а также хидерный файл GRPACK.H могут быть оставлены без изменения. Этот альтернативный вариант графического пакета GRPACK 1 для Турбо Си (версия 1.5) приведен ниже: /*GRPACK1: */ /* Графический пакет, использующий графические функции, */ /* доступные в компиляторе Турбо Си версии 1.5. */ /* Модель памяти: Huge (для ВСЕХ используемых модулей). */ /* Обеспечивается работа: */ /* Расширенного графического адаптера (EGA, 640 х 350) */ /* Цветного графического адаптера (CGA, 640 * 200) */ /* Графического адаптера Геркулес (HGA, 720 * 348) */ 8**
228 Глава 4. ПРОГРАММЫ D3D И PLOTHP #lnclude<stdio.h> #include<dos.h> #include<conio.h> #include <process.h> #mclude<string.h> #include<alloc.h> #lnclude <graphics.h> union REGS regs; int in_textmode-1, X max, Y max, drawmode-1, adaptype; FILE*fplot-NULL; float x_max»10.0, y_max-7.0, horfact, vertfact; static int X1, Y1, foregroundcol, backgroundcol, g_driver, gmode; static long int startaddress; void to_text(void); void textXY(int X, int Y, char *str); int IX(float x){ return (intXx*horfact-K).5); } int IY(float y) { return Y_max-(intXy*vertfact-K).5); } static void error(char *str) /* Отображается сообщение и выполнение программы завершается */ { if (lintextmode) to_text(); printf("%s\n,,,str);exit(1); } void clearpage(void) /* Очистка страницы */ { elearviewportQ; } int iscolor(void) /* Определение типа используемого адаптер*/ { detectgraph(&g_driver, &g_mode); if(g__mode — MCGAHI)g_mode-MCGAMED; if (g_mode — ATT400HI) g_mode - ATT400MED; if(g_mode —VGAHI)g_mode-VGAMED; return gjdrlver —CGA I I g_driver —MCGA I I g_driver —ATT400? 1 : g_driver—EGA I I g_driver—EGA64 I I g_driver—EGAMONO I I g_driver—VGA? 2 : g_driver— HERCMONO ? 0: -1; } /* 640 x 200 */ /* 640 x 350 */ /* 720 x 348 */
4.3. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ 229 static void setgrcon(int adaptype) /* Установка графических констант */ { startaddress - (adaptype — 2 ? OxAOOOOOOO: 0xB8000000); if (adaptype —2) { X_max-639;Y_max-349; /* EGA */ }else if (adaptype— 1) { X_max - 639; Y__max - 199; /* CGA */ }else { X__max-719;Y_max-347; /* HGA */ } } static int grbrfun(void) /* Используется в функции 'ctrlbrk' для определения операций, */ /* выполняемых при консольном прерывании */ { to_text(); return 0; Переход в текстовый режим перед выходом ! */ } void initgr(void) /* Инициализация графического режима */ { static int again-O; int errcode; if (again) setgraphmode(gmode); else { adaptype-iscolor(); /* Определение драйвера и режима */ if (adaptype < 0) error ("Неверный дисплейный адаптер"); ctrlbrk(grbrfun); /* Установка программы прерывания в Турбо Си */ initgraph(&g_driver, &g_mode, "Wturboc"); errcode - graphresult(); if(errcode<0) { printf ("Код ошибки в графическом пакете: %d", errcode); exit(1); > again- 1; setgrcon(adaptype); } horfact - X max/x_max; vertfact - Y max/y_max; if (adaptype —0) /* HGA */ { setvisualpage(l); setactivepage(l); } in_textmode4D; foregroundcol-getcolor(); backgrbundcol-getbkcolor();
230 Глава 4. ПРОГРАММЫ D3D И PLOTHP static Int txtbrfun(vold) { return 0; } void to_text(vold) /* Возврат в текстовый режим */ { If (!in_textmode) restorecrtmode(); ln_textmode- 1; ctrlbrk(txtbrfun); /* Восстановление обработчика прерывания по умолчанию */ } void endg^vold) /* Ожидание нажатия на любую клавишу */ /* и переход в текстовый режим */ { getch(); tojext(); } void dot(lntX, Int Y) /* Подсветка или гашение пиксела */ { putplxel (X,Y. (drawmode —0? (getpixel(X, Y) — foregroundcol ? backgroundcol: foregroundcol ): (drawmode— 1 ?foregroundcol: backgroundcol ) ) /* drawmode - 1 : подсветка */ ); /* drawmode - -1 : гашение */ } /* drawmode - 0 : переключение */ void checkbreak(void) { charch; If (kbhlt()){ ch-getch(); kbhltQ; ungetch(ch);} } void drawJlne(lntX1JntY1, int X2, Int Y2) /* Черчение отрезка прямой из (X1.Y1) в (Х2, Y2), */ /* где X1.Y1.X2, Y2 в пикселных координатах */ { if (drawmode— 1) line(X1, Y1. Х2, Y2); /* Значения 0 и -1 для параметра 'drawmode' для этой */ /* функции не применимы. См. также функцию 'dot()\ */ }
4.3. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ 231 static void fatai(void) /* Вывод диагонали и ожидание нажатия любой клавиши, */ /* после чего окончательный переход в текстовый режим */ { drawjine(0, Y max, X max, 0); endgrQ; } static void check(int X, int Y) /* Если точка (X,Y) вне границ экрана, то вызов функции */ /* 'fatal', отображение неправильных координат и останов */ { if(X<0 II Х>Х_тах II V<0 I I Y>Y__max) { fatalQ; printf /* Point outside screen (X and Y are pixel coordinates) */ (*Точка вне экрана (X и Y в пикселных координатах):\п"); printf("X-%d Y-%d\n",X,Y); printf("x-%10.3f y-%10.3f\n", X/horfact. (Y_max-Y)/vertfact); printf( "X_max-%d Y__max-%d x_max- %f y_max-%f\nM, X max, Y max, x_max, ymax); printf("horfact- %f vertfact- % f\n", horfact, vertfact); exit(1); } } void move(float x, float y) /* Перенос текущей точки в (х, у); х и у в экранных координатах */ { intXX.YY; XX - IX(x); YY -1Y(y); check(XX, YY); if(fplot!-NULL&&(XX!«X1 II YY!-Y1)) fprintf(fplot, "%6.3f %6.3f 0\n", x, y); X1-XX;Y1-YY; void draw(float x, float y) /* Черчение отрезка прямой из текущей точки в точку (х, у) */ { intX2,Y2; Х2 - IX(x); Y2 -1Y(y); check(X2, Y2); drawJine(X1,Y1,X2,Y2); if(fplotl-NULL) fprlntf(fplot, "%6.3f %6.3f An", x. y); X1-X2;Y1-Y2; } int pixlit(intX, int Y) /* Запрос, не подсвечен ли пиксел (X, Y) */ { return (getpixel(X, Y) — foregrounded); }
232 Глава 4. ПРОГРАММЫ D3D И PLOTHP static void prchar(char ch) /* Посылка байта ch на параллельный порт принтера */ { regs.x.dx-O; /* Выбор принтера */ regs.h.ah-O; /* Посылка байта из AL на принтер */ regs.h.al-ch; /* Байт для посылки на принтер */ int86(0x17, &regs, &regs); } void printgr(int Xlo, int Xhi, int Ylo, int Yhi) /* Печать на матричном принтере содержимого прямоугольника */ { Intnl. п2, ncols, I, X, Y, val, Xhl1; charline[720]; /* 720>-X_max */ regs.x.dx-O; /* LPT1 */ regs.h.ah -2; /* Код 2: Выборка состояния принтера */ int86(0x17, &regs, &regs); /* Опрос принтера */ if((regs.h.ah &8) — 8) /* Засылка состояния принтера в АН */ { if (intextmode) printf (" В текстовом режиме"); else textXY(0, Y max- 15, "Принтер не готов"); return; } prchar(27); prcharfV); /* Между строками 7/72 дюйма */ for(i-Ylo;K-Yhi;H-7) { checkbreakQ; /* Проверка консольного прерывания в ДОС */ XhM-Xlo-1; for(X-Xlo;X<-Xhi;X++) { val-0; for(Y-i;Y<i+7;Y++) { val«-1; val l-(Y>Yhl ? 0: plxlltpC. Y)); } line[X]-val; If (val) Xhi 1-Х; } ncols- XhH-Xlo+1; if (ncols) { n1-ncols%256;n2-ncols/256; prchar(27); prcharCL'); prchar(n1); prchar(n2); for(X-Xlo; X<-Xhl1; X-H-) prchar(line[X]); } prcharCXn'); } prchar(27); prcharC®');
4.3. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ 233 void setprdim(void) /* В этой функции устанавливаются такие значения */ /* хглах и углах, что графический результат будет */ /* выведен на печать с соблюдением точных соотношений */ /* размеров по горизонтали и по вертикали */ { extern float хглах, углах; extern int X max, Y max; setgrcon(iscolorQ); x_max«(X_max + 1)/120.0; y_max - (Y_max + 1)/72.0; } void textXY(int X, int Y, char *str) /* Отображение строки str в графическом режиме, */ /* начиная с точки (X, Y) */ { Int height, width; height-textheight(str); width -textwidth(str); setviewport(X, Y, X+width, Y+height, 0); clearviewport(); /* Стирание старого текста, если он был */ outtext(str); setviewport(0,0, X max, Y max, 0); void text(char *str) /* Отображение строки str, начиная с текущей точки (Х1, Y1) */ { textXY(X1,Y1,str); Xl4-strlen(str)*8; check(X1,Y1); } void imove(int X, int Y) /* Теперь (X, Y) будет новой текущей точкой */ { check(X,Y);X1-X;Y1«Y; } void idraw(intX, int Y) /* Черчение отрезка прямой из текущей точки в точку (X, Y) */ { check(X.Y); drawJine(X1,Y1,X,Y); X1-X.Y1-Y; } void wrscr(int row, int col, char *str) /* Запись кодов символов в память дисплея при текстовом режиме. */ /* row-0,1 24; col-0,1 79 */ { gotoxy (col+1, row+1); cputs(str); }
234 Глава 4. ПРОГРАММЫ D3D И PLOTHP void clearscrfvoid) /* Должна использоваться в текстовом режиме. Не путать */ /* с функцией 'clearpage', применяемой в графическом режиме. */ { clrscr(); } void settxtcursor(int row, int col) /* Установка курсора в текстовом режиме V { gotoxy(col+1, row+1); } void far * far_graphgetmem(unsigned size) { char*p; p - farmalloc((long)size); If (p — NULL) error ("He хватает памяти для функции 'graphgetmem'"); return p; } void far _graphfreemem(void far *ptr, unsigned size) { farfree(ptr); } При намерении использовать пакет GRPACK1 очень важно обратить внимание на третий аргумент в обращении к функции initgraph, которая вызывается из функции initgr. Этот аргумент, фигурирующий в руководстве под именем pathtodriver ("марш- рут к драйверу") должен указать, где в системе искать графиче- ский драйвер. Эти графические драйверы являются файлами с именами, заканчивающимися расширением .BGL Например, именем HERC.BGI обозначен файл для графического адаптера Геркулес. Эти драйверы поставляются на дисках, которые содер- жат компилятор Турбо Си, версия 1.5, библиотеки и т.п. Если в имеющейся системе они размещены в каталоге turboc текущего дисковода, то можно записать "Wturboc" как записано в приве- денном исходном тексте программы. Здесь необходима двойная обратная косая черта, потому что одиночная пара символов V оз начала бы "горизонтальную табуляцию" (особенность языка Си!). Если нужного графического драйвера нет в указанном каталоге, тогда программа initgrraph будет искать его в текущем каталоге. Можно также задавать пустую строку "" вместо реаль- ного каталога, в этом случае графический драйвер должен нахо- дится в текущем каталоге.
4.3. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ 235 Последние две функции graphgetmen и graphfreemen заме- няют аналогичные функции по умолчанию, используемые фун- кцией initgraph Турбо-Си (которая в свою очередь вызывается функцией, initgr) для распределения памяти для графического драйвера. Специфическая первая строка в этих функциях (с многократным появлением ключевого слова far) была написана в соответствии с Руководством по дополнению и расширению Турбо Си, версия 1.5. В связи с главной программой, например, D3D, которая сама распределяет большие объемы памяти, дейст- вительно необходимо включить эти две функции. Это вызвано тем, что соответствующие функции по умолчанию используют функцию malloc вместо farmalloc, но стало очевидным лишь пос- ле многократных безуспешных запусков программы на выполне- ние, обычно заканчивающихся системной аварией. Пакет GRPACK1 не полностью эквивалентен GRPACK. При работе с пакетом GRPACK действие функций draw, idraw, drawjine и dot зависит от значения переменной drawmode. Как уже говорилось, по умолчанию эта переменная имеет значение, равное 1. Если ей присвоить значение -1, то упомянутые функ- ции будут гасить пикселы, а при drawmode = 0 состояние каждого пиксела этими функциями изменяется на противоположное. В пакете GRPACK 1 это относится только к функции dot, но не к функциям вычерчивания линий. Это происходит из-за того, что для вычерчивания линий применяется функция line Турбо Си, которая не обладает способностью инвертировать состояние пик- селов в рассматриваемой линии. Инвертирование пикселов (при drawmode = 0) применяется в программе D3D как для курсора, так и для перпендикуляров, опускаемых из позиции курсора на плоскость ху в операциях с курсором. Заметим, что мы должны не только подсвечивать пик- селы, образующие курсор, и гасить их при перемещении курсо- ра, но также удалять и другие линии, когда через них проходит курсор путем двойного инвертирования состояния пиксела (пер- вый раз при появлении курсора, а второй раз при удалении кур- сора с этой точки). После таких операций исходное состояние всех пикселов должно сохраниться. К счастью, сам курсор фор- мируется с помощью функции dot и, поскольку мы можем запро- сить состояние пиксела (используя функцию getpixel в Турбо Си), всегда можно изменить основной цвет (подсвечен) на цвет
236 Глава 4. ПРОГРАММЫ D3D И PLOTHP фона (темный) и наоборот. С "черным ящиком" функции line этого нельзя сделать, но относительно программы D3D это не столь важно. В этой программе вертикальные линии, соединяю- щие курсор с плоскостью ху, не имеют существенного значения, поэтому вместо поиска более сложного решения эти линии мож- но просто исключить. Учитывая это замечание, в пакете GRPACK1 принято реше- ние, что функция drawjine вычерчивает отрезки прямых только в том случае, когда параметр drawmode имеет значение 1. В од- ной и той же версии модуля D3D только что упомя'нутые верти- кальные линии будут опущены, если применяется модуль GRPACK1, и вычерчены (и удалены впоследствии), если приме- няется модуль GRPACK. Исходный текст модуля GRPACK больше модуля GRPACK 1, но это не должно приводить к опрометчивым выводам. При ис- пользовании пакетов GRPACK и GRPACK 1 для программы D3D были получены выполняемые файлы D3D.EXE и D3D1.EXE со- ответственно. Последний файл был на 11639 байт больше перво- го, но даже и эта цифра может быть неверной. Не следует забы- вать, что программа D3D1.EXE загружает графические драйве- ры во время выполнения, а для программы D3D.EXE этого не де- лается. При графическом адаптере Геркулес это означает, что графические функции Турбо Си используют дополнительно 10046 байт во время выполнения. То есть в целом при использо- вании пакета GRPACK 1 потребуется примерно на 21 Кбайт боль- ше памяти, чем для пакета GRPACK. Это та цена, которую при- ходится платить за дополнительные возможности графики Тур- бо Си. Но чтобы исключить неправильную оценку, справедливо- сти ради следует отметить, что большое количество этих полез- ных свойств совершенно оправдывает эту цену. Если сравнивать обе программы D3D.EXE и D3D1.EXE экс- периментально, используя машину со сравнительно медленным процессором 8088, то можно обнаружить, что в общем первая программа работает быстрее, чем вторая. Другая разница заклю- чается в форме шрифтов в графическом режиме. В графических подпрограммах Турбо Си по умолчанию размер символов равен 8x8, тогда как в пакете GRPACK он составляет 11 * 8, как было описано в книге автора МГПК. Разницу в этих двух шрифтах можно видеть из рис. 4.1.
4.4. ФУНКЦИИ ГРАФИКИ НИЖНЕГО УРОВНЯ 237 Программа DX Доступные команды : X/Y/Z Управл.курсором ? Значения координат М-режим (А-оси и т.а.) [F1 (Помочь) I Q-выход S-шаг I С-очиститъ 11-веоэ I D-удалить JF-граии I L-список R-чтение I W-эались Е-весь экр1 Н-цдвидимы iV-t.зрения I Т-преобр. Вводите команду! (а) 1 Программа D30 Доступам команду : Н-реким (й-оси и т.д.) FK Помощь)! Q-выход S-oar ! С-очиотить 1-ввод 1 D-удалить F-грани I L-описок R-чтонио I Ы-эапись Е-весь экр! Н-нсвидимы и-т.ороиин! Т-пРООбр. Вводит* команду: (б) Рис. 4.1. Разница в шрифтах, (а) Шрифт, описанный в пакете GRPACK, (б) шрифт графики в Турбо Си (по умолчанию), доступный при использовании пакета GRPACK1
238 Глава 4. ПРОГРАММЫ D3D И PLOTHP 4.4. УДАЛЕНИЕ НЕВИДИМЫХ ЛИНИЙ Возможно, что наиболее интересной командой в программе является команда Я, которая позволяет в изображении объекта удалить невидимые линии. Весьма существенная часть книги автора "Принципы программирования в машинной графике4' (которую далее будем кратко обозначать ППМГ), была посвя- щена проблеме удаления невидимых линий, поэтому здесь нет смысла повторять детальное описание. Вместо этого рассмотрим наиболее существенные аспекты алгоритма определения невиди- мых линий и используемых структур данных. В основном приме- няется тот же самый алгоритм, который использовался в про- грамме HIDLINPIX, и описанный в ППМГ. (Эта программа по- лучила убедительное доказательство своей полезности, что было продемонстрировано В.Д. Мэем в его статье "Трехмерные изо- бражения по контурным картам", опубликованной в журнале Dr.Dobb's Journal of Software Tools, ноябрь 1987). Но вместо этой программы теперь будем использовать функцию, содержащуюся в модуле HLPFUN, обращение к которой производится из про- граммы D3D при вводе команды Я. По сравнению со своим пред- шественником, упомянутым выше, модуль HLPFUN содержит ряд усовершенствований и новых свойств: 1. Он может обрабатывать кривые поверхности, о чем упоми- налось почти в самоу конце параграфа 1.5. 2. Память для этого модуля отводится более сложным образом в том смысле, что принимается во внимание доступный объ- ем памяти. 3. Чтобы избежать сложностей с размером стека, была исклю- чена рекурсия. 4. Улучшена методика декомпозиции многоугольников на треугольники, теперь ее можно применять для любого мно- гоугольника. В последующем обсуждении будут часто упоминаться имена программных переменных. Это позволяет упростить отслежива- ние их в программе, но, поскольку программный модуль HLPFUN довольно сложный, это будет легче осуществить после прочтения всего данного текста. Многоугольник, вводимый пользователем с помощью коман- ды F, обеспечивает нам как отрезки прямой линии, являющиеся
4.4. УДАЛЕНИЕ НЕВИДИМЫХ ЛИНИЙ 239 кандидатами на вычерчивание, так и грани, которые могут за- крывать такие отрезки полностью или частично). Номера вер- шин каждого многоугольника (его также называют полигоном) запоминаются в непрерывной области памяти, доступ к ней обес- печивается с помощью указателя, сохраняемого в "массиве" pface. Фактически pface является переменной типа указателя, которой приписывается начальный адрес динамически распреде- ляемой области памяти. Это показано на рис. 4.2. Имя pface мож- но рассматривать как обозначение массива, элементами которо- го являются указатели на целые числа. (В языке Си указатель на целое число фактически может быть указателем на последова- тельность целых чисел). Pface 0 12 ... п ^~ ^^ п • Рис.4.2. Структура данных для граней Для обозначения целого числа, на который указывает эле- мент массива pface[j] можно использовать запись либо * pface \j], либо pface\j][Q]. Оно равно числу номеров вершин, следующих за этим целым числом.Так, если pface[j][Q] m /г, то элементы с pface [J ] [ 1 ] по pface [j][n] будут номерами вершин /-того полиго- на. Если известен номер i вершину, то можно найти ее видовые координаты дсе, уе, ze в структуре VERTEX[i] (здесь VERTEX фактически является переменной типа указателя, которой мы приписываем значение переменной р в модуле D3D). Стороны полигона представляют собой отрезки прямых ли- ний, которые нужно начертить в тех случаях, когда они видимы. Для простоты все полигоны разбиваются на треугольники, кото рые используются для определения видимости отдельных эле- ментов. Тогда каждый данный отрезок прямой мы можем срав- нить со всеми треугольниками и определить, не окажутся ли
240 Глава 4. ПРОГРАММЫ D3D И PLOTHP какие части этого отрезка не закрытыми любым из треугольни- ков, и тогда эти части (размеры которых могут составлять от ну- ля до полного отрезка) должны быть вычерчены. Такое решение привело бы к огромным затратам вычислительного времени. На- пример, если есть 1000 отрезков и 1000 треугольников, то это со- ставило бы 1 000 000 различных пар из одного отрезка и одного треугольника. В более ранней книге автора ППМГ был описан способ уменьшения этого количества пар, этот способ будет при- меняться и здесь. При рассмотрении конкретного отрезка прямой необходимо иметь средство для построения разумно ограничен- ного поднабора из набора всех треугольников и это желательно выполнить даже без анализа всех треугольников на данном эта- пе. Ключ лежит в определении экрана на который проецируются как отрезки прямых, так и треугольники. Чтобы пояснить этот способ наиболее простым образом будем считать, что наимень- шая область вывода, в которой размещается все изображение, выглядит как шахматная доска с 64 клетками. Идея заключается в том, что в каждой клетке будем запоминать информацию о тре- угольниках, перспективное изображение которых содержит точ- ки, принадлежащие этим клеткам. Чтобы наиболее четко пред- ставить полезность этой идеи, обозначим буквами от Л до Я все восемь столбцов (считая слева направо) и цифрами от 1 до 8 ряды (снизу вверх). Теперь если, для примера, изображение некото- рого треугольника, лежащего вблизи нижнего левого угла захва- тывает только квадраты А1, В\, 2?2, тогда тем или иным образом мы запишем некоторую информацию в этих квадратах. Затем позднее, анализируя отрезок прямой линии, мы снова будем оп- ределять, какие квадраты затрагиваются его изображением. Если, например, с отрезком PQ ассоциируются квадраты Е5, D6, С7, Z?8, то тогда выбираются только те треугольники, информа- ция о которых записана в именно этих квадратах, а это означает, что упомянутый выше треугольник, лежащий в нижнем левом углу шахматной доски, должен быть проигнорирован. Таким об- разом мы можем сформировать сравнительно небольшой набор треугольников, которые могут закрывать отрезок PQ и, анализи- руя PQ, проигнорировать все остальные. Фактически приходится рассматривать даже меньше треу- гольников, чем предусматривается этим описанием. Если для данного квадрата существует несколько треугольников, покры-
4.4. УДАЛЕНИЕ НЕВИДИМЫХ ЛИНИЙ 241 вающих этот квадрат полностью (что означает попадание всего квадрата внутрь изображения этих треугольников), тогда можно игнорировать все эти треугольники за исключением ближайшего к точке наблюдения. То, что мы здесь называли квадратом, в книге ППМГ было названо приборно-независимым пикселом, отсюда было получе- но имя модуля HIDLINPIX и имя HLPFUN для модуля функций, описываемого в даннрм параграфе. Что касается данной книги, то слово пиксел здесь применяется в обычном, приборно-зависи- мом смысле, и было бы нелогично применять тот же самый тер- мин к несколько более крупной части экрана. Поскольку такая часть экрана фактически является прямоугольником, а не квад- ратом, то далее будем применять именно этот термин. К тому же не весь экран, а только его некоторая часть, образующая прямо- угольник с горизонтальными и вертикальными сторонами, кото- рый полностью охватывает двухмерное изображение, будет по- делено на N х N равных прямоугольников. В версии программы, приведенной в Приложении Б принято N = 15. Значение N не сказывается на конечном результате, поэтому его нельзя связы- вать с некоторого рода "разрешающей способностью", но выбор размера прямоугольников влияет на время вычислений. Автор обнаружил, что для персонального компьютера фирмы IBM вре- мя вычислений обычно заметно увеличивается, если значение 15 заменить на значительно меньшую или на значительно большую величину (например, на 4 или на 30). Ассоциируем только что упомянутые прямоугольники с эле- ментами массива SCREEN. Элемент массива SCREEN[i]\j] (где / и у неотрицательные целые числа меньше Л0 соответствует пря- моугольнику в /-том столбце и в у-том ряду. Другими словами, значение переменной i вычисляется по координате X, а у по У. Ради простоты будем также применять слово прямоугольник для самих элементов массива SCREEN и будем говорить, что инфор- мация о треугольниках запоминается в прямоугольниках. Поскольку все наши прямоугольники имеют одинаковые раз- меры, а каждый прямоугольник должен быть ассоциирован с пе- ременным количеством треугольников, то фактически будем за- поминать треугольники вне прямоугольников, но таким обра- зом, что прямоугольники могут сообщить нам, где их можно най- ти. Будем использовать таблицу TRIANGLE со структурой
242 Глава 4. ПРОГРАММЫ D3D И PLOTHP TRIANGLE[i ] для каждого треугольника. В этой структуре най- дем номера вершин Л, В> С для треугольника U а также коэффи- циенты я, Ь, с, h уравнения ах + by + cz - h определяющего плоскость, в которой лежит треугольник. После этого можно использовать простое целое число i для идентифи- кации треугольника. Следовательно, можно было бы запоминать несколько наборов номеров треугольников в каждом прямо- угольнике, если бы это было возможно. Практически возникает переменное число треугольников, которые должны быть ассоциированы с каждым прямоугольни- ком. Поэтому будем запоминать начальный указатель линейно- го списка в каждом прямоугольнике и, вместо запоминания но- мера треугольника в некотором прямоугольнике, будем запоми- нать его в линейном списке, который начинается в этом прямо- угольнике. Сначала автор использовал наиболее естественный способ реальными указателями и с распределением памяти для каждого индивидуального элемента списка. Однако это привело к серьезным затруднениям. Поскольку нам необходимо реализо- вать эту методику в функции hlpfun, которая многократно вызы- вается из программы D3D, нужно было не забывать освобождать все динамически распределенные области памяти в этой функ- ции до возврата в основную программу с тем, чтобы те же самые области памяти могли быть снова использованы впоследствии. Если изображаемый объект сложный, то линейные списки, со- держащие номера треугольников, могут быть очень длинными и их будет очень много. Оказалось, что освобождение памяти, за- нятой элементами списков, требует огромных затрат времени в таких ситуациях. Средний пользователь программы D3D может представить себе, что генерация изображения с удаленными невидимыми ли- ниями требует затрат достаточно большого вычислительного времени, но он (или она) будет крайне удивлен, что на ликвида- цию такого изображения нужно будет затратить почти столько же времени. Но поскольку это действительно было так, автор мо- дифицировал программу и реализовал линейный список в виде таблицы, получившей имя TRUST. При этом удаление всех ли- нейных списков достигалось простым удалением этой таблицы, что выполнялось одной операцией. Каждый вход в таблицу
4.4. УДАЛЕНИЕ НЕВИДИМЫХ ЛИНИЙ 243 TRUST мог быть использован как элемент линейного списка, а это структура со следующими двумя полями: ptria Целочисленное "указание" на вход в таблицу TRIANGLE. Так, если это целое число равно /, то TRIANGLE[i] является рассматриваемым треу- гольником. next Целочисленное "указание" на следующий эле- мент в линейном списке. (Если это целое число равно к > 0, то TRUST [к] будет этим следую- щим элементом. Если к равно -1, то следующего элемента нет). Первый элемент каждого списка указывается целым числом, записанным в масси- ве SCREEN, об этом подробнее ниже. Каждый "прямоугольник" SCREEN[i][j] экрана является структурой со следующими тремя полями: trjcov Целое число i, указывающее на ближайший тре- угольник TRIANGLE[i], полностью покрываю- щий весь прямоугольник. trjiist Число с плавающей точкой, которое равно рас- стоянию между ближайшим треугольником, на который указывает trjcov, и точкой наблюдения. start Целое число к, указывающее на первый элемент списка TRUST [к]. На рис.4.3 имеем tr__cjv= 3, tr_start = 2 и trjiist- 8.0. Послед- нее означает, что между точкой наблюдения и точкой Р треу- гольника 3 расстояние равно 8.0 и проекция Р' этой точки Р бу- дет центром анализируемого прямоугольника. Здесь имеем 8x8 прямоугольников (хотя в программе определено 15 * 15 треу- гольников). На рис.4.3 показана ситуация, которая может поя- виться в процессе построения экранных списков на основе задан- ных треугольников. После того, как это "первое сканирование" завершено, любой треугольник, на который указывает элемент SCREEN[i][j].tr__cov, включается в начало линейного списка, начинающегося в SCREEN[i]\j].start. В нашем примере это оз- начает, что треугольник 3 будет включен в линейный список рас- сматриваемого прямоугольника (если только для этого треуголь- ника потом не встретится другой треугольник с расстоянием до точки наблюдения меньше, чем 8.0).
244 Глава 4. ПРОГРАММЫ D3D И PLOTHP SCREEN -dist. I cov-3 B 2-start TRIANGLE А В С а Ь с h О 1 2 3 TRUST P_tria next Рис. 4.3. Экран, экранные списки и таблица треугольников Если имеем дело с полигонами, то доступны не только треу- гольники, но и отрезки прямой линии. Они также должны быть где-то записаны в память, чтобы их можно было использовать впоследствии для вычерчивания, если они будут видны. Однако этого не следует делать безусловно. Во-первых, нужно вспом- нить параграф 1.7 (см. рис. 1.17), где говорилось, что номера вер- шин могут записываться в память со знаком минус, обозначая
4.4. УДАЛЕНИЕ НЕВИДИМЫХ ЛИНИЙ 245 тем самым искусственное ребро в полигонах с отверстиями. Оче- видно, что нам нужно просто игнорировать такие ребра. По- скольку это очень простой случай, об этом больше не будем гово- рить и будем рассматривать только положительные номера вер- шин. Во-вторых, нужно твердо помнить, что каждое ребро яв- ляется линией пересечения двух соседних граней и поэтому оно появляется в списке дважды, а так как его необходимо вычерчи- вать не более одного раза, то лучше всего нужно проигнориро- вать его второе появление. Эти два аспекта не являются новыми, они обсуждались ранее в книге ППМГ. Но имеется и третье усло- вие для каждого подлежащего вычерчиванию отрезка, но кото- рое появилось только в данной книге. Напомним, что программа D3D приспособлена также для вычерчивания изображений кри- волинейных поверхностей: если угол между векторами нормалей двух пересекающихся ограничивающих граней меньше некото- рой пороговой величины, заданной пользователем, то линия пе- ресечения этих двух граней вычерчиваться не должна. Это озна- чает, что для каждого ребра, который становится доступным в качестве стороны полигона и который мы хотим записать в па- мять, мы должны запоминать не только номера вершин Р и Q его концевых точек, но также и три составляющие вектора нормали. Заметим, что эти значения равны коэффициентам при перемен- ных х, у, z в уравнении плоскости, в которой лежит этот полигон. Таким образом мы сможем использовать вектор нормали [а Ъ с ], когда то же самое ребро встретится как сторона другого поли- гона, для которого также будет задан вектор нормали. Существуют некоторые линии пересечения, которые при их видимости должны быть вычерчены, даже если упомянутый вы- ше угол между нормалями меньше заданного порогового значе- ния. Это так называемые контурные линии объекта. Чтобы по- нять это, рассмотрим цилиндр на рис. 2.6. Должны быть вычер- чены самые крайние правые и левые вертикальные контурные линии, даже если1 они являются ребрами, аналогичными другим опущенным ребрам. Для их вычерчивания не нужно применять никаких специальных мер, поскольку каждая контурная линия является линией, в которой видимая грань встречается с задней гранью и поэтому она будет рассматриваться только однажды. Хотя выше об этом ничего не говорилось, но полигоны, определя- ющие заднюю грань, полностью игнорируются: во-первых, если
246 Глава 4. ПРОГРАММЫ D3D И PLOTHP они и закрывают какие-то точки, то эти точки также закрывают- ся видимыми гранями, и, во-вторых, их ребра либо закрыты, либо проявляются как ребра видимых граней. Напомним, что пользователь должен задать вершины каждого полигона в поряд- ке обхода против часовой стрелки и вторая вершина обязательно должна быть выпуклой. Тогда задние грани можно отличить от видимых граней по ориентации первых трех вершин после их проецирования. Если они обходятся не против часовой стрелки, значит рассматриваемая грань является задней. Поэтому очень хорошо, что контурное ребро анализируется только однажды, поскольку принадлежность к задней грани не приведет к вклю- чению его в список отрезков. Теперь становится ясно, что каждое ребро PQ, которое пре- тендует в качестве кандидата на занесение в память, должно быть сначала проверено, не является ли оно копией ребра, уже ранее занесенного в память. Если так, то явно незачем записы- вать в память новую копию, но кроме этого возможно потребует- ся удалить и старую копию (и, тем самым, уменьшить на едини- цу количество записанных ребер). Конечно, последнего никогда не произойдет, если задано нулевое пороговое значение. Чтобы ребра можно было просматривать более эффективно, будем записывать их в линейные списки, показанные на рис. 4.4. Для любого ребра PQ будем при необходимости менять местами вершины Р и Q, чтобы номер вершины Р всегда был меньше номера Q. Тогда номер вершины Q записывается как элемент линейного списка, начальная точка которого находится в VERTEX [р]. (Напомним, что VERTEX это таблица, в которой можно найти координаты всех вершин). Кроме номера вершины Q будем также запоминать коэффициенты а, Ъ, с, принадлежа- щие плоскости, в которой мы только что нашли ребро PQ, чтобы VERTEX хе ye ze connect i a b с next i a b с next *п I 1 I I ~~M6I I Puc.4.4. Линейные списки для ребер
4.5. УТИЛИТА ДЛЯ ПЛОТТЕРОВ ФИРМЫ HEWLETT-PACKARD 247 можно было выполнить "пороговую проверку", когда позднее становится доступной копия отрезка PQ. На рис. 4.4 показано, как записываются в память отрезки прямой (1 -5) и (1 -6). Пояснения остальных подробностей программы можно найти в книге автора ППМГ и в исходном тексте программного модуля HLPFUN, приведенном в Приложении Б. 4.5. УТИЛИТА ДЛЯ ПЛОТТЕРОВ ФИРМЫ HEWLETT-PACKARD Существуют две причины, по которым твердая копия наших графических результатов, полученных на матричном принтере, имеют не очень высокое качество. Во-первых, принтер не обес- печивает достаточно хорошего контраста между черным и белым и, во-вторых, для принтера мы использовали разрешающую спо- собность видеодисплея, а при этой разрешающей способности линии воспроизводятся довольно грубо, особенно на цветном графическом адаптере CGA с разрешающей способностью всего 640 х 200. Наши сравнительно сложные вычисления в трехмер- ном пространстве, особенно при удалении невидимых линий, дают в результате очень точные значения координат концевых точек каждого отрезка прямой линии в двухмерном пространст- ве. Эти данные можно было бы использовать, если бы в нашем распоряжении" были хорошие устройства вывода графической информации, поскольку программа D3D имеет средства для записи этих двухмерных результатов в файл. Напомним, что это можно осуществить вводом команды О (сокращенное обозна- чение для названия "Output file" — "выходной файл") после команды Я или команды Е. Этот тип файла должен четко отли- чаться от тех файлов, которые мы называли объектными файла- ми. С этой целью для объектных файлов будем всегда применять расширение имени .DAT (эти файлы будут использоваться в ка- честве входных файлов для программы D3D( а выходные файлы, получаемые по команде О, всегда имеют имена, заканчивающи- еся расширением .PLT. Например, если программа D3D прочи- тала объектный файл CUBE.DAT (или осуществила запись тако- го файла в качестве выполнения команды W), то после этого по команде О будет сформирован выходной файл CUBE.PLT. По- следнее имя будет приписано файлу и в том случае, если имя объектного файла начиналось со слова CUBE, но вопреки совету
248 Глава 4. ПРОГРАММЫ D3D И PLOTHP автора имело расширение, отличное от .DAT, или совсем без рас- ширения. Если пользователь не указал имени файла, поскольку не было использовано никакого входного файла или не задава- лась команда W, то по команде О будет сформирован выходной файл с именем NONAME.PLT. Для выходных файлов программы D3D (которые были сфор- мированы по команде О и имеют расширение имен .PLT) будем применять название "файлы черчения". Файлы черчения имеют очень простой формат: это файлы в кодах ASCII с одинаковыми строками в виде х у code где х и у представляют собой двухмерные координаты в диапазо- нах чисел от 0 до 10 и от 0 до 7 соответственно. Третий элемент code может быть либо 0, означающий "перо поднято", либо 1 в качестве кода для "перо опущено". Начало системы координат располагается в нижнем левом углу некоторого виртуального ли- ста бумаги. Например, мы можем вычертить прямой угол по сле- дующим данным: 2.000 6.000 0 2.000 1.000 1 5.500 1.000 1 Здесь предполагается, что перо сначала переместится в точку с координатами х = 2, у= 6. Затем оно вычертит отрезок прямой линии в точку х = 2, у = 1, а затем прочертит отрезок прямой ли- нии в точку х = 5.5, у=1. Оба положения пера ("поднято" = 0 или "опущено" = 1) во время перемещения пера при выполнении вы- шеописанного процесса могут также обозначаться словами move и draw. Тогда приведенные три строки текста символически мож- но обозначить как move(2.0, 6.0); draw(2.0,1.0); draw(5.5,1.0); Поскольку имена move и draw присвоены реальным функци- ям на языке Си, описанным в параграфе 4.3, то вполне очевидно что превращение файла черчения в реальный чбртеж является сравнительно простой задачей. Эта задача выполняется програм- мой PLOTHP. Первоначально автор написал эту программу иск- лючительно для получения чертежа на плоттере фирмы Hewlett-
4.5. УТИЛИТА ДЛЯ ПЛОТТЕРОВ ФИРМЫ HEWLETT-PACKARD 249 Packard, но затем появилась плодотворная идея приспособить эту программу для вывода изображения на видеодисплей или матричный принтер, если возникает такая потребность. Про- грамма запускается вводом команды PLOTHP после чего на экране появляется строка Имя входного файла: говорящая, что необходимо ввести имя файла черчения, напри- мер, такое как CUBE.PLT. Если такой файл действительно су- ществует, то последовательно появятся три следующих запроса: Вывод на видеомонитор? (Y/N): Вывод на матричный принтер? (Y/N): Вывод на плоттер Хьюлетт-Паккард? (Y/N): Поэтому даже если нет доступного плоттера, то все равно программа PLOTHP может оказаться полезной. Хотя мы уже имели возможность получить вывод графической информации на экране видеодисплея или на матричном принтере при работе программы D3D, может оказаться желательным повторить такой же вывод позднее, когда на печатающем устройстве будет уста- новлена новая красящая лента, и если изображаемый объект очень сложный, то программа PLOTHP отобразит его значитель- но быстрее, чем это сделает программа D3D, поскольку в ней не выполняется никаких вычислений в трехмерном пространстве. Если ответом на первый запрос будет нажатие на клавишу Y ("Да"), то появится запрос на ввод коэффициента масштабиро- вания. Если, например, желательно получить изображение в половину его нормального размера, то нужно ввести число 0.5, но в большинстве случаев будем использовать коэффициент мас- штабирования, равный 1. (Это находится в резком противоречии с коэффициентом масштабирования для плоттера, что будет обсуждено ниже). Промасштабированная с заданным коэффи- циентом картинка появится на экране видеодисплея и, при желании, на матричном принтере. Если читатель обладает счастливой возможностью доступа к плоттеру фирмы Hewlett-Packard, тогда можно будет использо- вать наиболее интересные возможности программы PLOTHP, ответив нажатием на клавишу Y ("Да") на последний из трех
250 Глава 4. ПРОГРАММЫ D3D И PLOTHP приведенных выше запросов. Плоттер должен быть подключен к последовательному порту ввода/вывода, обозначенному либо СОМ1, либо COM2 в терминологии MS-DOS. Есть целый ряд па- раметров последовательного порта, которые будут запрошены при самом первом обращении к опции порта: Введите номер послед, порта (1 для сот1,2 для сот2): Скорость в бодах (110,150,300, 600, 1200,2400,4800,9600): Если для последующих параметров точных значений не знаете, то попробуйте возможные варианты в указанном порядке: Проверка на четность (0 - нет, 1 - нечет, 2 - чет): Число стоповых бит? (1 или 2): Размер кода символов (8 или 7): После каждого из шести появившихся запросов нужно ввести один из предлагаемых ответов. Самым лучшим решением было бы ознакомиться с этими данными в технической документации на компьютер и плоттер. Ответ можно найти по положениям переключателей DIP на плоттере, которые, например, устанав- ливают скорость работы в бодах. На различных плоттерах, с ко- торыми пришлось поработать автору, на последние три запроса наилучшими ответами были 0, 1,8, так что можно попробовать сначала эти значения, если будет затруднительно найти точные данные в инструкциях. Было бы очень неприятно, если бы нужно было отвечать на все эти шесть запросов при каждом запуске программы PLOTHP. Поэтому автор счел целесообразным запи- сать ответы на эти вопросы в "файл конфигурации" и это было сделано следующим образом. Программа PLOTHP просматри- вает текущий каталог на предмет поиска файла с именем PLCONFIG.TXT. Поскольку при первом пуске программы тако- го файла может и не быть, то она запросит шесть вышеуказанных параметров, затем создаст файл PLCONFIG.TXT и в него будут записаны введенные параметры. Тогда при последующих пусках программы она найдет этот файл, будет считывать из него значе- ния параметров и, чтобы их можно было заменить, будет выво- дить из на экран видеодисплея с запросом правильности. Если данные не подходят, то пользователь может (и должен) ввести для них новые значения. После ввода параметров последовательного порта пользова- тель должен ввести коэффициент масштабирования, который применяется к координатам, считываемым из файла. Напомним,
4.5. УТИЛИТА ДЛЯ ПЛОТТЕРОВ ФИРМЫ HEWLETT-PACKARD 251 что в файле значения координат х изменяются в пределах от 0 до 10, и координат у — от 0 до 7. А для плоттера требуются очень большие значения целых чисел, например, от 0 до 11420. Пос- леднее значение представляет максимальную величину коорди- наты х, возможную на плоттере типа НР-7225А/В. Эти значения выражены в плоттерных единицах причем минимальное значе- ние плоттерной единицы равно 0.025 мм. Средства для масшта- бирования имеются и в самом плоттере, но поскольку это очень простая операция, масштабирование осуществляется внутри программы PLOTHP на основе коэффициента, введенного поль- зователем. Не всегда удобно ориентировать чертеж на физиче- ские ограничения плоттера и вместо значения 11420 можно уста- новить, например, значение 8000 для верхнего предела по коор- динате jc, что означает необходимость ввода коэффициента мас- штабирования, равного 800. В результате размер чертежа по длине составит 800 * 10 = 8000 плоттерных единиц, что эквива- лентно 8000 х 0.025 = 200 мм. При этом размер по оси у будет равен 800 * 7 * 0.025 = 140 мм. Читатель вскоре убедится, что этот коэффициент является наилучшим. Очень важно запом- нить, что коэффициент масштабирования в случае плоттера дол- жен быть довольно большим числом (например, 800). Наконец, можно изменить скорость перемещения пера при вычерчивании линий, обычно выбираемую по умолчанию. Вме- сто максимальной скорости по умолчанию (которая была равна 25 см/с на плоттере, установленного у автора) более низкое зна- чение (между 1 и 25) может дать лучшие результаты, так как чернила будут более равномерно достигать бумаги. При отрица- тельном ответе на вопрос Скорость работы по умолчанию? (Y/N): появится запрос на ввод желаемой скорости и немедленно после этого ответа плоттер начнет работать на основе файла черчения. Многие рисунки в этой книге были сделаны таким образом и можно считать, что их качество намного лучше, чем у некоторых других иллюстраций, полученных на матричном принтере. Данное описание программы PLOTHP предназначено для пользователя, которому нет необходимости знать проблемы про- граммирования. Далеее остановимся на некоторых технических деталях программирования. В руководстве по плоттеру фирмы
252 Глава 4. ПРОГРАММЫ D3D И PLOTHP Hewlett-Packard можно найти пояснения примерно по 40 коман- дам графического языка фирмы Hewlett-Packard (HP-GL), из которых мы будем применять только пять: IN Инициализация VS Выбор скорости PU Перо поднять PD Перо опустить РА Черчение в абсолютных единицах Рассмотрим, например, следующую строку команд IN;VS4;PU;PA4000,3000;PD;PA4000,5000;PU; Будучи посланной на плоттер, эта строка вызовет выполне- ние следующих действий по шагам: 1. IN инициализирует плоттер. 2. VS выберет скорость, равную 4 см/с. 3. PU поднимет перо. 4. РА переместит перо в точку с координатами X - 4000, Y =3000. 5. PD опустит перо. 6. РА вычертит отрезок прямой линии из установленной ранее точки в п.4 в точку с координатами X = 4000, У = 5000. 7. PU поднимает перо. Программа PLOTHP считывает строки текста из заданного файла черчения и преобразует их в команды языка HP-GL, каж- дая из которых заносится в текстовую строку str и затем посы- лается на последовательный порт вывода путем обращения к функции ser_str(str); Функция ser_str посылает все символы сА, предшествующие завершающему пустому символу в строке str, на последователь- ный порт вывода с помощью функции ser_char(ch); Эти обе функции наши собственные, первая из них не пред- ставляет каких-либо сложностей для опытного программиста на языке Си, но вторая требует некоторых знаний из области пере- дачи данных, что увидим ниже.
4.5. УТИЛИТА ДЛЯ ПЛОТТЕРОВ ФИРМЫ HEWLETT-PACKARD 253 Для обслуживания последовательного интерфейса (RS232) имеется специальный вызов из ROM-BIOS ("Базовая система ввода/вывода в постоянном запоминающем устройстве: БСВВ- ПЗУ"). Этот интерфейс доступен через прерывание 20 (шестнад- цатеричное значение 0x14), как подробно описано в руководстве по программированию Питера Нортона для персональных ком- пьютеров фирмы IBM (The Peter Norton Programme? s Guide to the IBM PC), Есть четыре вида операций, пронумерованных от 0 до 3. Перед программным прерыванием программист на языке ас- семблера должен занести номер операции в регистр АН и, в слу- чае операции с кодом 1, занести код символа, подлежащего пере- даче, в регистр AL. Кроме того, в регистр DX нужно занести код последовательного порта (0 для СОМ1 и 1 для COM2). Посколь- ку мы должны использовать язык Си, а не язык ассемблера, для этой цели можно применить обращение к функции Ш86, кото- рая использовалась в пакете GRPACK также и для других целей (см. параграф 1.2.6 в предыдущей книге автора МГПКили пара- граф 4.3 в данной книге). Эта функция доступна во многих реа- лизациях языка Си на персональных компьютерах фирмы IBM, включая компиляторы Турбо Си, Латтис Си и Майкрософт Си. Хотя автор довольно успешно применял функцию Ш86 в первой версии программы PLOTHP, впоследствии она была заменена на функцию bioscom, описание которой было обнаружено позднее в руководстве по Турбо Си. Функцию bioscom оказалось легче применять чем int86, по- скольку она менее общая, что делает программу PLOTHP лучше читаемой. Имя bioscom по всей видимости означает "связь через BIOS" и эта функция может быть использована только для последовательной связи. Вместо обращения к регистрам подобно языку ассемблера, в данном случае можно просто выразить наше намерение через аргументы функции, как предлагается в прото- типе функции: int bioscom(int cmd, char byte, int port); Эта строка находится в заголовочном файле bios.h, который вследствие этого потребуется включить в нашу программу. Бо- лее полное объяснение работы функции Ыосот можно найти в Руководстве по компилятору Турбо Си. Здесь же обсудим только то, что нам требуется. В первом аргументе указывается номер
254 Глава 4. ПРОГРАММЫ D3D И PLOTHP операции, упомянутый выше, который может принимать одно из следующих значений: 0 устанавливает параметры связи равными значению, ука- занному в переменной byte. 1 посылает символ из переменной byte на последовательный порт. 2 получает символ с последовательного порта, его можно най- ти в младших восьми битах возвращаемого целочисленного значения. 3 возвращает текущее состояние последовательного порта. Если cmd = 0, то второй аргумент показывает, как установить параметры связи. Этот аргумент с именем byte должен интерпре- тироваться как последовательность восьми бит, пронумерован- ных 7, ,6 , ..., О слева направо. Есть четыре группы бит в пере- менной byte: 7, 6, 5 скорость в бодах, закодированная числами 000, 001,..., 111 для значений 110,150,300,600,1200, 2400, 4800, 9600 бод соответственно. 4, 3 00, 01, 11 — как коды для проверки на четность: "Нет", "Нечет" и "Чет" соответственно. 2 0 = один столовый бит, 1 = два стоповых бита. 1,0 10 = 7 бит данных, 11=5 бит данных. Третий аргумент — port является кодом для номера порта: равен 0 для СОМ 1 и 1 для COM2. Хотя все это может звучать слишком техничным, но это совсем нечтрудно реализовать, поскольку понятия о скорости в бодах, проверки на четности о количествах стоповых бит и битах данных хорошо известны. Когда все это было сделано, то про- грамма хорошо работала для некоторых простых текстовых при- меров, но стала неправильно работать для более сложных черте- жей из-за появления переполнения буфера. В руководстве по плоттеру была обнаружена глава о протоколе совместимости, которая вероятно была написана для инженеров электронщиков, а не для программистов. В параграфе "Режим совместимости технических средств" была обнаружена блок-схема в котдрой показан цикл ожидания и, хотя некоторые технические термины в нем были не очень понятны, тем не менее ее было достаточно,
4.5. УТИЛИТА ДЛЯ ПЛОТТЕРОВ ФИРМЫ HEWLETT-PACKARD 255 чтобы найти иное решение, которое хорошо работает: void ser_char(char ch) { int retval; do { retval - bloscom(3, byte, port); } while ((retval & 0x0030)!- 0x0030); bioscom(1, ch, port); } Перед посылкой символа ch на последовательный порт (при номере операции 1) процессор ожидает в цикле do-while, пока он не сможет это сделать. Внутри цикла производится запрос сос- тояния последовательного порта (используя номер операции 3). Функция bioscom, при обращении к ней с номером операции 3, выдает в качестве возвращаемого значения текущее состояние порта. Каждый бит этого целого числа имеет определенный смысл, который полностью может быть понятен только опытным специалистам по передаче данных. Но как это следует из шест- надцатеричной константы 0x0030 в приведенном тексте про- граммы, из этой переменной выбираются только 5 и 4 биты, оз- начающие "Готовность набора данных" и "Очистка для пере- дачи" соответственно. Выполнение цикла прекращается только тогда, когда оба этих бита в возвращаемом числе равны 1. В этом случае можно предотвратить переполнение буфера данных в плоттере. Остальные подробности могут быть ясными из исход- ного текста программы PLOTHP. Если читатель захочет вос- пользоваться этим исходным текстом, он не должен забывать, что для работы программы необходим заголовочный файл GRPACK.H, поэтому он должен подключаться обязательно, а для редактора связей нужно подключить модуль GRPACK.OBJ. /* PLOTHP: Основное назначение этой программы заключается в преобразовании входного файла типа xxx.PLT в чертеж на плоттере фирмы Хьюлетт-Паккард. Кроме того эта программа^может отобразить тот же чертеж на видеодисплее или напечатать его на матрич+юм принтере. Входной файл должен состоять из строк вида х у с Координаты х и у должны быть выражены в виде вещественных чисел, их значения обычно не должны превышать 10.0 и 7.0 соответственно, а код с может быть либо 0 (перо поднять), либо 1 (перо опустить). */
256 Глава 4. ПРОГРАММЫ D3D И РЬОТНР #include<stdlo.h> #include<conlo.h> #include<bios.h> ♦include <ctype.h> ♦include <process.h> ♦Include "grpack.h" void display(int printer); void plotter(void); void initplotter(void); float scallngfactor(void); void ser_str(char *p); void ser_char(char ch); void initialize(void); char getcapital(void); FILE*fp; charfllnam[40]; unsigned char byte; Int penposition, port; main() { charch; printf ("Имя входного файла:"); scanf("%s", filnam); fp-fopen(filnam, "r"); If (fp — NULL) {printf ("Проблемы с файлом"); exit(1);} printf ("Вывод на видеомонитор? (Y/N):")', ch-getcapital(); if(ch — 'Y') { printf ("ХпВывод на матричный принтер? (Y/N):"); ch - getcapital(); display(ch —'Y'); fclose(fp);fp-NULL; } printf ("ХпВывод на плоттер Хьюлетт-Паккард?(У/^:"); ch-getcapltal(); if (ch — 'Y') { if (fp —NULL) { fp-fopen(filnam, "r"); if (fp —NULLXprintf ("Проблемы с файлом"); exit(1);} } plotted); } exit(0); /* Закрытие файла fp, если был открыт */ } void display(lnt printer) { float f, x, y, xfact, yfact, fact; charch, code; f-scalingfactor(); If (printer) setprdim(); /* Изменение xmax and ymax (float) V initgr(); /* Установках max and Y max (int) */ xfact - f*x_max/10.0; yfact - f*y_max/7.0; fact - (xfact < yfact ? xfact: yfact);
4.5. УТИЛИТА ДЛЯ ПЛОТТЕРОВ ФИРМЫ HEWLETT-PACKARD 257 while (fscanf(fp, u%f %f %сГ, &x, &у, &code) —3) { ch - getc(f p); if (code <0 II code>1 II ch!-'V) { textXY(40,40, "Неверный формат файла"); endgr(); exit(1); } x*- fact; y*- fact; if (x > x_max I I y > y_max) { to_text>; printf ("Вне границ: х - %6.3f у - %6.3An", х, у); return; if (code) draw(x, y); else move(x, y); } if (printer) printgnj), X max, 0, Y__max); endgr(); } void plotter(void) { float f, x, y; int ix, iy, code, count-O, velo; charstr[50], ch; inltplotterQ; printf ("ХпКоэффициент масштабирования f для плоттера обычно очень большой."); printf ("ХпНапример.если х или у во входном файле изменяются от 0 до 10,"); printf ("\па в системе координат плоттера их значения должны лежать в"); printf ("Хппределах от 0 до 8000, то коэффициент f будет равен 800.")*, f-scalingfactor(); printf ("ХпСкорость работы по умолчанию? (Y/N): *); ch -getcapltalQ; lf(ch — 'N') { printf ("\nВведите целое число, равное скорости в см/с\п"); printf ("(но не менее, чем 1, и не более, чем 127):"); scanf(M%d",&velo); sprintf(str, "VS%d;", velo); ser_str(str); } while (fscanf(fp, "%f %f %d", &x, &y, &code) — 3) { ch - getc(fp); if (code <0 II code>1 II ch!-'\n') { printf ("Неверный формат файла"); exit(1); } count++; ix-(intXf*x + 0.5); iy-(intXf *y + 0.5); if (code!- penposition) { ser_str(code ? "PD;": "PU;"); penposition-code; 9-273
258 Глава 4. ПРОГРАММЫ D3DM PLOTHP sprintf(str, ttPA%04d,%04d;*\ ix, ly); ser_str(str); } prlntf("\n%d точек \n", count); serstrCPU;"); } void initplotter(void) { FILE*fplconfg; int code, comnr, baud, parity, stopblts, charsize, oldvalues-O, retval; charanswer-'Y'; fplconfg -fopen(nplconfig.txt", ur"); if(fp!confg!-NULL) oldvalues-(fscanf(fplconfg, M%d %d %d %d %d", &comnr, &baud, &parity, &stopbits, &charsize) —5); if(oldvalues) { printf ("ХпПараметры последовательного порта по умолчанию:\п\п"); prlntf ("Номер последовательного порта: %4d\n", comnr); printf("Скорость в бодах: %4d\n", baud); рг1птД"Проверка на четность: %4s\n", (parity —2? "чет": parity? "нечет": "нет")); рг1п#("Число стоповых бит: %4d\n", stopbits); printf(" Длина кода символов: %4d bits\n", charsize); printf("Bce эти данные правильны? (Y/N): и); answer - getcapital(); printf("\nn); } lf(oldvalues —0 11 answer—'N') { printf ("\пВведите номер послед, порта (1 для com1,2 длясот2): и); scanf("%d". &comnr); code--1; while (code —-1) { printf(M\nCKopocTb в бодах\п"); printfC'(110,150.300,600, 1200,2400,4800,9600): "); scanf("%d",&baud); code-(baud— 11070: baud— 1507 1: baud— 30072: baud—60073: baud —12007 4: baud —24007 5: baud —48007 6: baud —9600? 7:-1); if (code —-1) printf ("Скорость в бодах неверна, повторите ввод\п"); } printf ("\п\пЕсли для последующих параметров точных значений не знаете," "\пто попробуйте возможные варианты в указанном порядке:\п\п"); printf ("Проверка на четность (0- нет, 1 - нечет. 2 - чет): и); scanf("%d", &parity); printf ("Число стоповых бит? (1 или 2): м); scanf("%d", &stopblts); printf ("Размер кода символов (8 или 7):"); scanfC%d". &charsize); If (fplconfg !- NULL) fclose(fplconfg);
4.5. УТИЛИТА ДЛЯ ПЛОТТЕРОВ ФИРМЫ HEWLETT-PACKARD 259 fplconfg -fopenC'plconfig.txt", "w"); fprintf(fplconfg. "%d %d %d %d %d\n", comnr, baud, parity, stopbits, charsize); fclose(fplconfg); } if (parity — 2) parity - 3; /* Внутренний код для *чет' равен 3 */ port-comnr-1; /*0для coml, 1 для com2 */ byte-(code «5) I (parity «3) I ((stopbfts-1)«2) I (charsize -5); retval - bioscom(0, byte, port); /* Проверка: передающий регистр сдвига пуст, */ /* передающий регистр удержания пуст, */ /* данные готовы. */ If ((retval & 0x6100)!-0x6100) { рпптД"\пОшибка инициализации: "); printf(MAH - %02XH\n", retval »8); exit(1); } se^strC'INiPU;"); /* Инициализация */ penposition-0; } float scalingfactonVoid) { float f; printf (и\пВведите коэффициент масштабирования f\n"); printf("(f- 1: натуральный размер\п"); printf(" f > 1: увеличение\п"); prlntfC f < 1: уменьшение): tt); scanf(M%f", &f); getchar(); /* Символ перевода строки пропускается */ return f; } void ser_str(char *p) { while (*p) ser^char(*(p-H-)); } void ser_char(char ch) { int retval; /* Проверка: Набор данных готов, очистка перед передачей. */ do { retval - bioscom(3, byte, port); } while ((retval & 0x0030)!- 0x0030): bioscom(1,ch, port); } char getcapital(void) { charch; ch-getche(); return toupper(ch); } 9**
Приложение А ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ (Исходный текст модуля D3D) /* D3D: Проектирование в трехмерном пространстве. После компиляции этот модуль (D3D) должен быть отредактирован совместно с модулями: GRPACK(или GRPACK1)(см. параграф 4.3) HLPFUN (см. параграф 4.4) и TRAFO (см. параграф 3.7). Следует использовать компилятор Turbo С фирмы Borland. Модель памяти: Huge. */ #include<stdio.h> #include<math.h> #include<ctype.h> #lnclude<alloc.h> #lnclude<conio.h> #include<string.h> #include <process.h> #include <grpack.h> #include<dos.h> #defineBIG1e10 #definelBIG 30000 #defineEPS 1e-6 #defineMAXFACES2500 #deflneLIN1 190 #defineLIN2 210 #defineLIN3 230 #defineLIN4 250 void initrotate(double a1, double a2, double a3, double v1, double v2, double v3, double alpha),
Исходный текст модуля D3D 261 rotate(double x, double у, double z, double *px1, double *ру1, double *pz1), /* Функции 'initrotate' и 'rotate' определены bTRAFO*/ lnit_vlewport(vold), hlpfun(float rho, float theta, float phi, float surfllmlt), coeff(float rho, float theta, float phi), ermes(char *s); /* функции Inltvlewport, hlpfun и coeff определены в HLPFUN */ intabs(lntx); static void aspect(vold), getstr(int X, int Y, char *str), mv(float x, float y, float z), dw(float x, float y, float z), screencoor(int mode, int i, int *pX, int *pY), viewing(float x, float y, float z, double *pxe, double *pye, double *pze), display(void), plotpolnt(inti, int Bold), grmes(int meslin, char *s), rdflle(int normal), wrflle(void), cursorcontrol(char direc), cursor(float x, float y, float z, int new), show(char c), faces(void), eyepos(void), plotsph(float rho, float theta, float phi, Int down), enqulre(lnt vpos, char *str, float *px), textcursor(int col, Int line), checkfaces(int I), infopage(int i), helpinfo(void), myungetch(char ch), Hne^lntP.intQ), checkall(void), clearCvoid), zoom(int large), reposition(void), plotaxes(int checksize), fresh(void), list_faces(void), points(void), transform(void), check_alloc(intl), asksave(void), entire(void), openplotfile(void),
262 Приложение А. ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ close_plotfile(void), pressanykey(char *str); static int storepoint(int i, float x; float y, float z), rdnumber(lnt *p), rdoldnr(int *p), getcM(void); static char query(int line, char *s), mygetch(void); struct linsegface /* См. модуль HLPFUN */ {Int i; double a, b, c; struct linsegface *next;}; struct vertex { intinuse; float xw, yw, zw; double xe, ye, ze; struct linsegface *connect; }*p; float xO, yO. zO; double v11, v12, v13, v21, v22, v23, v32, v33, v43, PI,Pldlv180; float xmin, ymin, xmax, ymax; static float d, c2, d, dfirst, c2first, dfirst, rho, theta, phi, xxx, yyy, Xvprange, Yvprange, Xvpcenter, Yvpcenter, Xvpmin, Xvpmax, Yvpmin, Yvp_max, xwmin, ywmin, zwmin, xwmax, ywmax, zwmax, xcur, ycur, zcur, stepK).2, zemin, zemax; static int margin, modified, facespresent, zoomin, pridim, bufposition, inside, newcentralpoint, numbers-1, numO, axes, axesO, auxlines-1, auxO, boldK), boldO, plength, psize, faces_entering, points_enterlng, X max1, progr_arg; int nmax, **pface, nface, object_present; static char buffer[50], s2[2], char__avail, filnam[40], spaces[60]; void main(intargc, char *argv[]) { int I, lowest, highest; long Ibytes; float threshold, surflimlt, xlowest, xhighest; charstr[30],ch,ch0, ch1; if(argc>1) { strcpy(fllnam, argv[1J; progr_arg - 1; }
ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 263 Pl-4*atan(1.0); Pldiv180- PI/180; pface-(int **)farcalloc(MAXFACES. sizeof(int *)); psize - sizeof(struct vertex); lbytes-farcoreleft(); plength-lbytes/(3L* psize); /* Около трети свободной памяти отведено для вершин */ р-(struct vertex *)farcalloc(plength+7, psize); if (p - - NULL) егте5("Проблемы с памятью"); s2[1]-'\0'; margin-456; rho - 1000; theta -20; phi - 75; coeff(rho, theta, phi); clearscrQ; wrscr(1,10," Программа"); wrscr(3,10," 'ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ'"); wrscr( 6,10,u Описание этой программы содержится в книге"); wrscr(8,10," Л.Аммерал"); wrscr(9,10, " 'ИНТЕРАКТИВНАЯ ТРЕХМЕРНАЯ МАШИННАЯ ГРАФИКА'"); wrscr(12,10," перевод с английского языка книги"); wrscr(13, 7, "Ammeraal, L (1988), INTERACTIVE 3D COMPUTER GRAPHICS,"); wrscr(14, 16," Chichester: John Wiley & Sons."); if(prograrg) { wrscr(17,0, "Желательна ли разная толщина линий по глубине?{УМ):"); settxtcursorOe.O); ch- mygetch(); s2[0]-ch; if (isalpha(ch)) wrscr(16,0, s2); bold-(ch--'Y' llch--y); } wrscr(20,0, "Для помощи нажмите клавишу F1"); wrscr(22,0, "Для продолжения нажмите любую клавишу...."); settxtcursor(22,42); ch«mygetch(); if(ch--0&&getch()--59/*F1*/)helplnfo(); initgr(); X max1 -X max+1; for (H(X__max1 - margin - 16)/8; iW); i- -) spacesti}-''; /* Строка пробелов ; в конце символ '\0' */ zoom(0); /* Установка размера области вывода; 0 - малая область */ clearQ; display(); if (progr_arg) rdfile(O); /* Главный цикл; */ while (grmes(LIN1, "Вводите команду:"), textcursor(margin+140, LIN1), chO-mygetch(), ch-toupper(ch0), ch !- 'Q')
264 Приложение А. ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ { grmes(LIN2,м "); grmes(LIN3, tt M); grmes(LIN4, u "); if(chO--O) { ch1-mygetch(); if(ch1--59) /* Функциональная клавиша F1 */ helpinfo(); continue; > show(ch); if (isdigit(ch)) { if (points_entering){myungetch(ch); points();}else lf(faces_entering){myungetch(ch);faces();}else grmes(LIN2, "Нельзя начинать цифрой*1); continue; } if(ch — T) { points_entering - 1; pointsQ; continue; } pointsentering - 0; if(ch--'F') { faces_entering - 1; faces(); modified - 1; continue; > faces_entering-0; if(ch--X' I I ch--'Y' 11 ch--'Z') { cursorcontrol(ch); continue; } if(ch--'R')rdfile(1);else if (ch - - 'W') {wrfile(); modified - 0;} else If (ch - - 'V') eyepos(); else if (ch - - 'L') list_faces(); else If(ch--'C') { Milnam-AO^asksaveO; clear(); display(); }else if (ch--'?') { if(kbhit())getch(); grmes(LIN2, "Номер точки:"); getstr(margin+104, LIN2, str); if (sscanf(str, "%d", &i)!- 1 I I i >-nmax I I p[i].inuse--0) { grmes(LIN2, "Число (формат) неверны"); continue; } sprintf(str, l4%4.2f %4.2f %4.2Г, p[i].xw, p[i].yw, p[i].zw); grmes(LIN3,str); }else if(ch--'D') { if(kbhit())getch(); while (nmax >0 && p[nmax-1].inuse - -0) nmax- -; xlowest-nmax;
ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 265 enquire(LIN2, "Нижняя граница: ", &xlowest); lowest - (intXxlowest + 0.1); if (lowest < 1) lowest- 1; xhlghest-xlowest; enquire(LIN3, "Верхняя граница: ", &xhlghest); highest-(lntXxhlghest +0.1); If (highest >- nmax) highest - nmax - 1; If (lowest <- highest) { for(Howest; K-highest; i++) { p[i].inuse-0; checkfaces(i); } checkall(); modified- 1; display(); } }else if(ch--'M') { numbers- (query(LIN2, "Номера точек? (Y/N):") - - 'Y'); axesO - axes; auxO - auxlines; axes-(query(LIN2, /* Axes? */ "Oai?(Y/N):")--'Y'); if (axes) auxlines - (query(LIN2, "Вспом.линии? (Y/N): u) - - 'Y'); else auxlines-0; bold - (query(LIN2, "Толстые линии? (Y/N):n) - - 'Y'); if (axesO && !axes I I auxO && !auxlines)checkall(); /* Коррекция размера изображения, поскольку теперь оси */ /* удалены и для изображения доступно больше места. */ lf(query(LIN2, "Весьэкран?(Y/N):")--'Y') entlreQ; else if (numbers !-num0 11 axes!-axesO 11 auxlines HauxO I I bold !-boldO)display(); }else if (ch - - 'E') entireQ; else If (ch - - 'P') printgr(0.0, X_max, 0.0, Y_max); else if (ch--,S')enquire(LIN2, "Размер шага:", &step); else if (ch - - T') transform(); else if(ch--'H') { clearpage(); to_text(); if(kbhlt())getch(); wrscr(0,0, "Нужно ли, чтобы грани объекта аппроксимировались" " как поверхность? (Y/N):"); settxtcursor(0,73); ch - mygetch(); s2[0^ch; lf(ch>32)wrscr(0,73, s2);
266 Приложение А. ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ if(ch--'Y' llch--y) { wrscr(2,0, "Введите 'пороговое* значение в градусах. Тогда линии пересечения"); wrscr(3.0, "двух соседних граней, при их видимости, будут вычерчены только в"); wrscnM, О, "том случае, если угол между векторами нормалей этих двух граней"); wrscr(5,0, "будет больше'порогового'значения. По умолчанию используется"); wrscr(6,0, "значение 35 градусов, если будет нажата только клавиша 'Enter'."); settxtcursor(7, 15); ch - getchar(); if(ch--'\n' II (ungetc(ch, stdin), scanf("%f", threshold)!- 1)) threshold-35; surflimit - cos(threshold*Pldiv180); }else surflimit-1.0; aspectQ; inltgr(); hlpfun(rho, theta, phi, surflimit); close_plotfile(); ch-mygetch(); lf(ch--'P' llch--'p') printgr(0, X max. 0, Y max); if (prldlm) { clearpage(); to_text(); x_max - 10.0; y_max - 7.0; prldlm - 0; initgrO; } display(); }else grmes(UN2, "Неверная команда"); /* "Invalid command*1 */ } asksaveQ; to_text(); } static void asksave(void) { charch; If (modified && nmax + nface > 7) { ch - query(LIN2, "Сохранить ? (Y/N):"); If (ch - -' Y' 11 ch - - 'y') wrflle(); grmes(LIN2,""); } } static void aspect() { charch; wrscr(8,0, "После отображения результата на экране его можно напечатать на");
ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 267 wrscr(9,0, "матричном принтере путем ввода команды Р.")', wrscr(10,0, "Если вместо этого будет нажата любая другая клавиша, то на"); wrscr(11,0, "экране восстановится предыдущее изображение."); wrscr(12,0, "Будем говорить, что используется точное отношение сторон, если"); wrscr(13,0, "окружность изображается как окружность, а не эллипс."); wrscr(14,0, "Нормально отношение сторон будет верным на экране."); wrscr(15,0, "Если же верное отношение сторон нужно обеспечить на принтере"); wrscr(16,0, "(при допускаемом неверном отношении на экране), то, пожалуйста,"); wrscr(17,0, "введите команду А. Можно ввести команду О, если необходимо"); wrscr(18,0, "получить выходной файл (xxx.PLT), который затем может быть."); wrscr(19,0, "например, прочитан программой PLOTHP для формирования изображе-"); wrscr(20,0, "ния на плоттере фирмы Хьюлетт Паккард или другом графопостроителе."); wrscr(21,0, "Если ничего этого не нужно, то нажмите любую другую клавишу..."); settxtcursor(22,0); ch - mygetch(); s2[0)-ch; if(ch>")wrscr(22,0,s2); if(ch-«'A' II ch--'a') { setprdimQ; pridlm- 1; }else if (ch--'0' 11 ch"'o')open_plotfile(); } static void checkall(void) /* Сжатие после удаления точки, если необходимо */ { fnti.count-O; float xw, yw, zw, ze; xwmin - ywmin - zwmin -zemin - BIG; xwmax - ywmax - zwmax - zemax --BIG; for(l-1;i<nmax;i++) lf(p[l].inuse) { COUnt++; xw-p[i].xw; yw-pfij.yw; zw - p[i].zw; ze-p[i].ze; if (xw < xwmin) xwmin - xw;
268 Приложение А. ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ if (xw > xwmax) xwmax - xw; if (yw < ywmin) ywmln - yw; if (yw > ywmax) ywmax - yw; if (zw < zwmin) zwmin - zw; if (zw > zwmax) zwmax - zw; if (ze < zemin) zemin - ze; if (ze > zemax) zemax - ze; } if (count - - 0) clear(); else { if (xwmax < 0.5) xwmax - 0.5; /* Чтобы устранить случай нулевой длины осей */ if (ywmax < 0.5) ywmax - 0.5; if (zwmax < 0.5) zwmax - 0.5; xO-BIG; /* Вынуждает вычислить новую центральнуюточку объекта */ reposition(); } } static void check_alloc(int i) { if (i >- plength) ermes ("Номер вершины слишком велик"); } static void checkfaces(int I) /* Точка i только что была удалена. Если какие-то грани включали */ /* вершину с номером I, то эти грани также удаляются. */ { intj.n, к; if (nface - - 0) return; grmes(LIN3, "Пожалуйста, ждите..."); for(J-0;j<nface;j-H-) { if (pfaceU] - - NULL) continue; n - pfaceLllO]; if (n<0) ermes(Mn<0 в функции checkfaces"); for(k-1;k<-n;k-H-) if(abs(pface[jlk])--i) { farfree(pface[JB; pface[J]- NULL; break; } } grmes(LIN3, u "); grmes(LIN4, u "); } static void clear(void) { static intflrst-1; inti.j; axes-auxlines-numbers- 1; for(i-0; Knmax; K+)p[l].inuse-0; p[0].xw - p[0].yw - p[0].zw - 0.0; nmax- 1;
ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 269 for(j-0;j<nface;j-H-) if(pface[j]!-NULL) { farfree(pface[JB; pface[j]- NULL; } nface - 0; xwmln - ywmin - zwmin - 0.0; xwmax - ywmax - zwmax - 1.0; xO-yO-zO-0.5; fresh(); reposltion(); if (first) { dfirst-c1;c2first-c2;dflrst-d; first-0; } modified - 0; objectpresent - 0; static void close_plotfile(void) { if(fplot!-NULL) { fclose(fplot); fplot-NULL; } } static void cursor(float xw, float yw, float zw, int new) { extern int drawmode; inti.j.dm, cnt; static int X, Y, X0, Y0; if (new) { inside-1; for(cnt-0; cnt<2; cnt-н-) { storepoint(nmax+4, xw, yw, zw); storepoint(nmax+5, xw, yw, 0.0); screencoor(1, nmax+4, &X, &Y); screencoor(1, nmax+5, &X0, &Y0); if (inside) break; else { reposition();display(); } } if (cnt - - 2) ermes(ncnt - 2 в функции cursor"); p[nmax+4].inuse - 0; p[nmax+5].inuse - 0; } dm - drawmode; drawmode - 0; forQ—2; j<=2; j-H-4) fo'r(i—4; i<-4; i++) dot(X+i, Y+j); dot(X-4, Y-1); dot(X-3, Y-1); dot(X+3, Y-1); dot(/+4, Y-1);
270 Приложение А. ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ dot(X-4, Y); dot(X-3, Y); dot(X+3, Y); dot(X+4, Y); dot(X. Y); dot(X-4, Y+1); dot(X-3, Y+1); dot(X+3, Y+1); dot(X+4, Y+1); draw_line(X, Y, X0, Y0); drawmode-dm; } static void cursorcontrol(char direc) { charc.cO, str[30]; float *axls, xO, yO, zO, dx, dy, dz, dist2, d2; Intl, j, Jumped-O; if (lauxlines) { axes-auxllnes-1;dlsplay(); } xcur «ycur-zcur-0.0; / cursor(xcur, ycur, zcur, 1); axis- (dlrec - - 'X' ? &xcur: dlrec - - 'Y' ? &ycur: &zcur); grmes(LIN2, "Вводите:"); grmes(LIN3,M +,-, I, J, или D"); grmes(LIN4, "(или нажмите Enter)"); while (cO - mygetch(), с - toupper(cO), c--'+' | | c--'-' 11 c — 'X' 11 c--'Y' 11 c--^* llc-T llc--'J' 11 с — 'D' llc--0) { If (c !-'D') jumped -0; lf(c--'+' lie--'-') { xO - xcur.yO- ycur; zO- zcur; If (c - -'-') *axls — step; else *axls +-»step; cursor(xO,yO,zO,0); cursor(xcur, ycur, zcur, 1); sphntf(str,u%5.2f%5.2f%5.2f ", xcur. ycur, zcur); grmes(LIN4+20,str); continue; } show(c); If (c - - 'X') {axis - &xcur; dlrec - c;} else If (c - - *Y') {axis - &ycur; dlrec - c;} else If (c - - 'Z') {axis - &zcur; dlrec - c;} else If(c--T) { for(i-1; Kplength; I++) If (p[l].lnuse- -0) break; check_alloc(l); If (I >- nmax) nmax -1+1; cursor(xcur, ycur, zcur, 0); /* Стирание */ storepolnt(l, xcur. ycur, zcur); modified- 1; plotpolnt(i, 1); cursor(xcur, ycur, zcur, 0); /* Вычерчивание снова */
ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 271 }else в If(c--'J') { if(nmax--O) { grmes(LIN2, "Нет точек в наличии"); cursorfrcur, ycur, zcur, 0); grmes(LIN3,w"); grmes(LIN4,u"); return; } dlst2-BIG; for(i-1;Knmax;l++) lf(p[i].inuse) { dx-p[i].xw-xcur; dy-p[i].yw-ycur; dz-p[i].zw-zcur; d2 - dx*dx + dy*dy + dz*dz; If(d2<dist2){dlst2-d2;j-l;} } Jumped-1; cursor(xcui\ ycur, zcur, 0); /* Стирание старого курсора */ xcur- p[j].xw; ycur- p[j].yw; zcur- p[)].zw; cursor(xcur, ycur, zcur, 1); sprintf(str, u%4d %4.2f %4.2f %4.2Г, J.xcur.ycur, zcur); grmes(LIN4+20,str); }else if(c--'D') { if (Ijumped) grmes(LIN4, "Сначала введите J"); else { pQJJnuse - 0; cursor(xcur, ycur, zcur, 0); checkfaces(j); checkall(); modified- 1; Jumped-0; display(); cursor(xcur, ycur, zcur, 1); grmes(LIN2, uВводите:"); grmes(LIN3, M+, —. I.J.илиD"); grmes(LIN4, "(или нажмите Enter)"); } }else /* с - -0: Возможно была нажата клавиша со стрелкой/ { If (getch()- - 59) helplnfoQ; else { grmes(LIN4+20,"He та клавиша нажата"); } } show(direc); } cursor(xcur, ycur, zcur, 0); /* Стирание KypcopV if (cO I- '\n* ЭД cO!- Лг') myungetch(c0); showC )i grmes(LIN2,""); grmes(LIN3, u "); grmes(LIN4,* "); grmes(LIN4+20\"");
272 Приложение Л. ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ static void display(void) { inti. J, к. n, drawn, iold; facespresent-O; clearpage(); if(axes)plotaxes(1); if (zoomin)init_viewport(); /* Определена в модуле HLPFUN */ else { imove(0,0); idraw(X max, 0); idraw(X max, Y max); idraw(0, Y__max); idraw(0, 0); imove(margin-4,0); idraw(margin-4, Y max); textXY(margin, 5, "Программа D3D"); textXY(margin, 22, "Доступные команды :"); textXY(margin, 40, "X/Y/Z: Управл.курсором"); textXY(margin, 54, "?: Значения координат"); textXY(margin, 68, "М -режим (А-оси и т.д.)"); textXY(margin,82, "Р1(Помощь)| Овыход"); textXY(margin, 96, "S-шаг I С-очистить"); textXY(margin, 110, "l-ввод I D-удалить"); textXY(margin, 124, "F-грани I L-список"); textXY(margin, 138, "R-чтение I W-запись"); textXY(margin, 152, "Е-весьэкр| Н-невидимы"); textXY(margin, 166, "V-т.зрения l Т-преобр."); grmes(LIN3, "Пожалуйста, ждите..."); } for (j-O; j<nface; j++) { if (pface[j]- - NULL) continue; facespresent- 1; /* См. функцию 'listfaces' */ i_old - abs(pface[jl1]); n - pface[jI0]; if (n<0) ermes("n<0 в функции display"); for(k-2;k<-n;k++) { i - pfaceUIk]; drawn - i >- 0; I - abs(i); if (drawn) Hne3(i_old, i); iold - i; } i - pface[JI1]; drawn - i >-0; i -abs(i); if (drawn) line3(i_old, i); for(i-1;Knmax;i-H-) if(p[l].inuse) plotpoint(i.O); if(lzoomin) { grmes(LIN1, "Вводите команду:"); grmes(LIN3,""); grmes(LIN4,""); numO - numbers; axesO - axes; auxO - auxlines; boldO-bold; } if (axes) plotaxes(O); /* Только чтобы различить обозначения х, у, z */
ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 273 static void dw(float x, float у, float z) { intX.Y; double xe, ye, ze; viewing(x-0.5, y-0.5, z-0.5, &xe, &ye, &ze); /*xO-yO-zO-0.5*/ X-IX(d*xe/ze+c1); Y-IY(d*ye/ze+c2); ldraw(X, Y); } static void enquire(int vpos, char *str, float *px) { charsnum[40], dum[40]; intlen, slen, j; charch; if(kbhit())getch(); Ien-8*strlen(str>f8; if(margin+len>X maxl) ermes ("Слишком длинная строка в 'enquire'"); textXY(margin, vpos, str); sprintf(snum, l4%3.1f", *px); slen-strlen(snum); if (snum[slen-1]- - '0' && snum[slen-2]- -'.') snum[slen-2]-'\0'; if (margin+len+8*strlen(snum)>X maxl) ermes ("Строка + число слишком длинные в 'enquire'"); slen - (X max - margin - len)/8; for (j-0; j<slen; j++) dum[j]-''; textXY(margin+len-8, vpos,M"); dum[slen]-'\0'; do { j-100; do textXY(margin+len, vpos, dum); while (--j&&!kbhit()); J-200; do textXY(margin+len, vpos, snum); while (—j &&!kbhit()); } while (!kbhit()); ch-mygetch(); if(ch--O) { ch-mygetch(); if(ch--59) ' /* клавиша F1 */ { helpinfoQ; textXY(margin+len, vpos, dum); }else
274 Приложение А. ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ if(ch--3) /* прерывание */ { to_text();exit(1); } }else if (ch - - An' 11 ch - - '\r') return; else myungetch(ch); while (1) { getstr(margln+len, vpos. snum); if (sscanf(snum, H%f\ px)-- 1) break; textXY(margln+len, vpos, dum); } } static void entire(vold) /* Использование полного экрана */ { float с 1 save, c2save, dsave; charch; dsave - d; c2save - c2; dsave - d; clearpage(); to_text(); if(kbhit())getch(); aspect(); /* Может вызвать изменение значений xmax и углах */ initgrQ; /* Вычисляются значения коэффициентов horfact и vertfact */ zoom(1); /* Изменение переменных Xvpmax и т.д. */ newcentralpoint - 0; /* Может быть изменена в функции 'reposition' */ reposition(); /* Вычисление новых значений с1, с2, d */ /* Может также измениться центральная точка */ displayQ; /* Вывод большого изображения */ close_plotfile(); ch-mygetch(); if (ch - - 'P' 11 ch - - 'p') printgr(0. X__max, 0, Y_max); clearpage{); to_text(); if(pridim) { x_max -10,0; y_max - 7.0; pridim - 0; } initgrQ; zoom(0); if (newcentralpoint) reposition(); /* Вычисление новых значений с1, с2, d */ else { d-dsave; c2-c2save;d-dsave; } dlsplayO;
ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 275 void ermes(char *s) { charch; if(in_textmode){printf(s);exit(1);} /* 'in_textmode' - внешняя переменная */ to_text(); printffs); if(!modified 11 nmax + nface<-7)exit(1); prlntf ("\п\пВыполнение программы D3D заканчивается An"); prlntf ("Желаете сохранить об'ект? (Y/N): u); ch-getche(); if(ch--'Y' II ch--y) { inltgrQ; wrflleQ; to_text(); } exlt(1); static void eyepos(vold) { float rho_1, thetaj, phi_1, rho 1, thetal, phl1, dtheta, dphl, rho2; intl,n-5; rho_1 - rho; theta_1 -theta; phM - phi; rho -1000; theta,- 20; phi - 75; coeff(rho, theta, phi); clearpage(); d - clflrst; c2 - c2first; d - dfirst; mv(0.0.0.0,0.0); dw(1.0,0.0.0.0); textCx"); mv(0.0,0.0.0.0); dw(0.0,1.0.0.0); text(Hy"); mv(0.0,0.0.0.0); dw(0.0,0.0,1.0); textfz"); mv(0.0,0.0,0.0); rho 1 -1.0; theta 1 - 40; phi 1 - 40; rho2-rho1*sin(theta1*Pldlv180); plotsph(rho2, thetal, 90.0,1); plotsph(rho1, thetal, phl1,1); dw(0.0,0.0,0.0); rho 1-0.9; dtheta-theta 1/n; rho2-0.9*rho2; mv(rho2.0.0,0.0); for (1-1; K-n; I++) plotsph(rho2, i*dtheta, 90.0,1); dphl-phl1/n; plotsph(rho1,0.0,0.0,0); for(l-1; K-n; i++) plotsph(rho1, thetal, l*dphl. 1); plotsph(0.5*rho1, thetal. phll, 0); text(MrhoH); plotsph(rho2.0.4*theta1,90.0,0); textCtheta"); plotsph(rho1, thetal, 0.4*phl1,0); text("phin); plotsph(1.2*rho1, thetal, phi 1,0); text(Tfla3");
276 Приложение А. ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ textXY(margin, 15, "Введите rho, theta. phi"); textXY(margin, 30. "(theta, rho в градусах)"); textXY(margin, 45, "При нажатии на Enter"); textXY(margin, 60, "будут использованы "); textXY(margin, 75, "отображаемые значения."); enquire(90, "rho -", &rho_1); enquire(105, "theta-", &theta_1); enquire(120, "phi - ", &phi_1); rho - rho_1; theta - thetal; phi - phi_1; coeff(rho, theta, phi); xO-BIG; /* Вынуждает 'reposition' вычислить новую ценральную точку */ reposition(); displayQ; } static void faces(void) { int i, n-0, draw, i_old-0; char str[30]; double xA, yA, zA, xB, yB, zB, xC, yC, zC, a, b, c, h; if ((pface[nface]-(int *)farcalloc(5, sizeof(int)))- - NULL) ermes("He хватает памяти"); grmes(LIN2, "Строка чисел с точкой:"); textcursor(margin, LIN3); bufposition--1; while (1) { if(rdnumber(&i)--0) break; draw - i >- 0; i - abs(i); п-н-; Hf(p[i].inuse--0) •*[ sprintf(str, "Неопр. точка: %6d", I); pressanykey(str); display(); faces_entering-0; return; } lf(n — 1) { xA - p[i].xw; yA - p[i].yw; zA - p[i].zw; }else "f(n — 2) { xB - p[i].xw; yB - p[i].yw; zB - p[i].zw; }else if(n--3) { xC - p[i].xw; yC - p[i].yw; zC - p[i].zw; h-xA*(yB*zC-yC*zB)- xB*(yA*zC-yC*zA) + xC*(yA*zB-yB*zA); a - yA * (zB-zC) - у В * (zA-zC) + yC * (zA-zB); b - -(xA * (zB-zC) - xB * (zA-zC) + xC * (zA-zB)); с - xA * (yB-yC) - xB * (yA-yC) + xC * (yA~yB); }
ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 277 else if (fabs(a*p[i].xw + b*p[i].yw + c*p[i].zw - h) > 0.001 *fabs(h) + EPS) { pressanykeyCHe в одной плоскости"); displayQ; /* Удаляются любые неверные строки */ faces_entering - 0; return; } if (n > 1 && draw) line3(i_old, i); i_old-i; if (n > 4) { if ((pface[nface] - (int *)farrealloc ( pface[nface], (n+1) * sizeof(int))) - - NULL) ermes(nHe хватает памяти"); } pface[nfaceln] - (draw ? i: -i); } If(n--O) { grmes(LIN2, "Неверное целое число"); farfree(pface[nfacej}; faces_entering-0; return; i-pface[nfaceI1]; if (I >— 0) Hne3(i_old, i); pface[nfacej[0]-n; nface++; if(nface--MAXFACES) { grmes(LIN2, "Слишком много граней "); nface—; } } static void fresh(void) { Inti.X.Y; xmin-ymin-zemin-BIG; xmax-ymax-zemax--BIG; /* подлежит изменению*/ /* Вычисление значений xmin, xmax, ymin, ymax: */ storepoint(nmax, 0.0,0.0, 0.0); storepoint(nmax+1, xwmax, 0.0,0.0); storepoint(nmax+2,0.0, ywmax, 0.0); storepoint(nmax+3,0.0,0.0, zwmax); screencoor(0, nmax, &X, &Y); screencoorfO, nmax+1, &X, &Y); screencoor(0, nmax+2, &Х, &Y); screencoor(0, nmax+3, &Х, &Y); for(i-nmax; i<nmax+4; i-H-)p[i].inuse-0;
278 Приложение А ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ static Int getcM(void) /* Вызывается только из 'rdnumber' */ { charch; lf(bufposition<0) { getstr(margln, LIN3, buffer); huf position -0; } ch - buffer[bufposition++]; If (8*bufposition>-X maxl -margin) { getstr(margin. LIN3, buffer); ch - buffer(0]; buf position - 1; }else lf(ch--'\0') { bufposltlon-4;ch-'\n'; } return ch; } static void getstr(int X, int Y, char *str) { charch; int i-0. first-1. j. k; while (1) { J-X+8*i; If (j >• X__max1-8) break; if (first) { for (k-X; k<X_max1-8; k-f-8) textXY(k, Y,M "); first-0; } textcursor(|, Y); ch-mygetch(); if(ch--'\n' 11 ch--V)break; if(ch--8) /* стирание символ a*/ { If (--КО) 1-0; }else { str[i]-s2[0]-ch; x textXY(j, Y, s2); I++; } } strlil-ЛО'; } static void grmes(int meslin, char *s) { int len; len - 8*strlen(s); If (!in_textmode) /* внешняя переменная */ { textXY(margin, meslin, spaces); if (margin+len > X maxl) { to_text(); printf("B функции 'grmes': \пи); printf("s-'%s' len-%d margin-%d", s, len, margin);
ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 279 exit(1); } textXY(margin, meslin, s); }else printf("%s\n",s); static void helpinfo(void) { int M, was_Jn_grmode; wasjngrmode - !in_textmode; /* внешняя переменная */ settxtcursor(24,37); if (was Jn_grmode) { clearpage(); to_text();} do { Infopage(i); If (1—1) wrscr(24,0, "Нажмите любую клавишу для продолжения ..."); else { wrscr(23,0, "Нажмите клавишу 'стрелка вверх1 для возврата на предыдущую страницу"); wrscr(24,0, "или любую другую клавишу для продолжения ..."); } if (mygetch()- - 0 && mygetchQ - - 72) { if(—I — 0) I — 1; }elsei-H-; }while(K4); if(wasJn_grmode) { lnltgr();dlsplay<); } static void lnfopage(int i) { lf(1~D { clearscr(); wrscr(0,50, "Страница 1 (всего З страницы)"); wrscr^.O, UD3D: Проектирование в трехмерном пространстве"); wrscr(3,0, "Автор L. Ammeraal (Нидерланды)"); wrscrp, 0, "Программа D3D позволяет формировать реалистичные изображения"); wrscr(6,0, "трехмерных об'ектов. Простейший способ заключается в использовании"); wrscr(7,0, "файла, подготовленного данной программой или любым другим способом."); wrscr(8,0, "Кроме этого можно начать и без входного файла и определить точки са-"); wrscr{9,0, "мостоятельно, используя команду ввода I, или непосредственно с по-");
280 Приложение Л. ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ wrscr(10,0. "мощью курсора. В первом случае после команды I нужно ввести положи-"); wrscr(11,0, "тельное целое число и затем трехмерные координаты х, у, z новой"); wrscr(12,0, "точки. Во втором случае для определения позиции точки применяется"); wrscr(13.0, "курсор. Для этого следует нажать на одну из клавиш X, Y, Z, затем на"); wrscr(14,0, "+ или -для перемещения курсора. Под управлением курсора команда Г); wrscr(15,0, "вводит новую точку, а команда J вызывает переход курсора в ближайшую"); wrscr06,0, "существующую точку. Командой S можно изменить шаг перемещения курсо-"); wrscr(17,0, "ра. Для отображения значений координат точки нужно ввести ее номер с"); wrscr(18,0, "предшествующим вопросительным знаком (?). Командой D можно удалить"); wrscr(19,0, "все точки с номерами в заданном диапазоне. В режиме курсора команда D"); wrscr(20,0, "удаляет только одну точку, выбранную по команде J. Команда М задает"); wrscr(21,0, "режим вывода номеров точек, осей, линий проецирования, управляет тол-"); wrscr(22,0, "щиной линий и размером экрана. "); }else if(l--2) { clearscrQ; wrscr(0,50, "Страница 2 (всего 3 страницы)"); wrscr(1,0, "После ввода нескольких точек можно с помощью команды F опреде-"); wrscr(2,0, "лить грани и вычертить отрезки прямых линий, указав список номеров"); wrscr(3,0, "точек, закончив его символом точки (.). Например: "); wrscr(4,0," F"); wrscrfoO," 1234 5."); wrscr(6,0, " Если после команды F вводится больше, чем две точки, то это оз-"); wrscr(7,0, "начает, что они являются вершинами полигона и все они должны лежать в"); wrscr(8,0,
ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 281 "одной плоскости (см. также команду R). После команды Н (см. ниже)"); wrscr(9,0, "эти полигоны будут выполнять роль граней трехмерного об'екта. Вершины"); wrscr(10, О, "должны быть заданы в порядке обхода против часовой стрелки (при наб-"); wrscr(11,0, "людении извне об'екта). Ошибки в команде F могут быть скорректирова-"); wrscr(12,0, "ны с помощью команды L Команда R выполняет считывание описания"); wrscr(13,0, "об'екта из файла, а по команде W об'ект записывается в файл. Измене-"); wrscr(14,0, "ние положения точки зрения выполняется командой V. Автоматическое"); wrscr(15,0, "удаление невидимых линий происходит при вводе команды Н. При задании"); wrscr(16,0, "запрашиваемого 'порогового' значения больше0 две соседние грани (с"); wrscr(17,0, "внутренним углом близким к 180 градусам) будут аппроксимировать кри-"); wrscr(18,0, "вые поверхности. Линия пересечения двух соседних граней вычерчивается"); wrscr(19,0f "только в том случае, если угол между их векторами нормалей будет"); wrscr(20,0, "больше 'порогового' значения. По команде Е для вывода изображения ис-"); wrscr(21,0, "пользуется весь экран. Команды Е и Н позволяют выводить изображения"); wrscr(22,0, "на матричный принтер и на плоттер. "); }else if(i--3) { clearscrQ; wrscr(0,5Q, "Страница 3 (всего 3 страницы)"); wrscr(1,0, " В пакете есть файлы с именами EXAMPLE1.DAT и т.д., которые пока-"); wrscr(2,0, "зывают структуру 'об'ектных файлов', записываемых по команде W и счи-"); wrscr(3,0, "тываемых по команде R. Сначала даются координаты всех точек в формате"); wrscr(4,0, u,n х у z', где п - номер точки. Далее может следовать ключевое слово"); wrscr(5,0,
282 Приложение Л. ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ "'Faces:' (Грани), после чего идет список последовательностей номеров"); wrscr(6, О, "точек (см. также команду F). Каждая последовательность завершается"); wrscr(7,0, "точкой (.) или символом #. Точки в каждой последовательности являются"); wrscr(8,0, "вершинами полигонов, выполняющих роль граней об'екта после ввода ко-"); wrscr(9,0, "манды Н. Стороны таких полигонов вычерчиваются при их видимости, за"); wrscr(10,0, "исключением отрицательных номеров точек. Например, для '8 3-52 7.'"); wrscr(11,0, "сторона 3 5 не будет вычерчена даже при видимости. Последовательность"); wrscr(12.0, "только из двух точек обозначает отдельный отрезок прямой линии. Ко-"); wrscr(13,0, "манда Т предлагает четыре вида преобразований, а именно: поворот, пе-"); wrscr(14,0, "ренос, масштабирование и отражение. Для начала рекомендуется восполь-"); wrscr(15,0, "зоваться командой R для чтения имеющегося файла, например,"); wrscr(16,0, "EXAMPLE1.DAT; затем попробовать задать команду V для изменения точки"); wrscr(17,0, "наблюдения и команду Е для вывода изображения на полный экран. Теперь"); wrscr(18,0, "рекомендуется применить команду Н для удаления невидимых линий. Если"); wrscr(19,0, "после этого появится желание начертить новую картинку самостоятельно,"); ' wrscr(20,0, "то предварительно нужно очистить экран с помощью команды С. "); } } static void Iine3(int P, int Q) { float ze1, ze2, zrange-zemax-zemln+1e-15; lntX1.Y1.X2.Y2.l1.l2.J1.J2; charstr[10]; static int dx[H0, 1.-1,0.0}, dy[H0. 0,0.1.-1}. n[H5.4,3,2.1}; screencoor(1, P, &X1, &Y1); /* Вычисляются значения ххх и ууу */
ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 283 If (zoom && fplot!- NULL) move(xxx, yyy); screencoor(1, Q, &X2, &Y2); if (zoom && fplot!- NULL) draw(xxx, yyy); if(!zoom II bold) { ze1 - p[P].ze; ze2 - p[Q].ze; 11 -(intX(ze1-zemln)/zrange * 4.999); !2-(lntX(ze2-zemln)/zrange * 4.999); If (iKO 11 I2<0II I1>4II I2>4) { to_text(); printf("H-%d l2-%d\nM, 11,12); printf(MP-%d\nM, P); prlntf(nQ-%d\nn,Q); prlntf(-ze1-%fze2-%fzemln-%fzrange-%f\n", ze1, ze2, zemln, zrange); exlt(1); } forGl-0;JKn[M];j1++) for(j2-0;j2<n[l2];J2++) drawJlne(X1+dxDl], Y1+dy[j1], X2+dx[j2], Y2+dy[j2]); }else draw_line(X1, Y1, X2, Y2); If (numbers) { sprlntf(str, tt%d*\ P); lmove(X1-2, Y1-5); text(str); sprlntf(str, "^d", Q); lmove(X2-2, Y2-5); text(str); } } static void llstfaces(void) { Int J, I, nch, nchO, count-O, n, nchmax; charstr(50], ch; nchmax - (X max-margin)/8; for(H);j<nface;j-H-) { if (pfaceD] - - NULL) continue; count++; n - pfacefJlO]; nch - 0; if (n<0)ermes("n<0 в списке граней list__faces"); for(l-1;K-n;i++) { nchO-nch; nch^-sprlntf(str+nch," %d", pfaceQIi]); If (nch > nchmax) { strfnchO] - Л0'; break;} } s2[0]-V; grmes(LIN2, str); textXY(margin + 8*strien(str), LIN2, s2); ch-query(LIN3, *OK?(Y/N):M); if(ch--'N') { farfree(pface[j]); pfaceQ]- NULL; display(); if (!faces_present && (! axes 11 lauxlines 11 inumbers)) { axes - auxlines - numbers -1; display();
284 Приложение А. ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ }else if (ch!-'Y') break; } if (count--0)grmes(LIN2. "Нет граней"); elsegrmes(LIN2,u"); grmes(LIN3,""); grmes(LIN4,""); Int m'atherr(struct exception *a) { lf(a->type--DOMAIN) { if (strcmp(a->name, "sqrt") - - 0) егтез("Ошибка при вычислении sqrt domain"); > егтеэСОшибка вычислений с плавающей точкой"); return(0); /* Вычисление прекращено из-за ошибки */ } static void mv(float x, float у, float z) { intX.Y; double xe, ye, ze; viewing(x-0.5, y-0.5, z-0.5, &xe, &ye, &ze); /*xO-yO-zO-0.5V X-IX(d*xe/ze+c1); Y-IY(d*ye/ze+c2); imove(X, Y); } static char mygetch(void) { charch; ch-getch(); if (ch - - 3) /* Ctrl-C или Ctrl-Break */ { if (!in_textmode) to_text(); exit(1); } char_avail-0; return ch; } static void myungetch(char ch) { ungetch(ch); char_avail - 1; } static void open_plotfile(void) { char *p, *q, plfilnam[40]; if(*filnam) { p-filnam; q-plfilnam; while (*p && *p !- V) *q++- *p++; strcpy(q, ".pit"); }else strcpy(plfilnam, "noname.plt"); fplot-fopen(plfilnam, "w");
ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 285 if (fplot — — NULL) ermes("Нельзя открыть файл черчения"); } static void plotaxes(int checksize) { int XO. YO, X1. Y1. X2, Y2, X3, Y3, cnt. i; if (checksize) { storepoint(nmax, 0.0,0.0,0.0); storepoint(nmax+1. xwmax, 0.0, 0.0); storepoint(nmax+2.0.0, ywmax, 0.0); storepoint(nmax+3,0.0,0.0, zwmax); screencoor(0, nmax, &X0, &Y0); screencoor(0, nmax+1, &Х1, &Y1); screencoor(0, nmax+2, &X2, &Y2); screencoor(0, nmax+3, &X3, &Y3); reposition(); inside- 1; } storepoint(nmax, 0.0,0.0. 0.0); storepoint(nmax+1, xwmax, 0.0,0.0); storepoint(nmax+2,0.0, ywmax, 0.0); storepoint(nmax+3,0.0,0.0, zwmax); screencoor(1, nmax, &X0, &Y0); screencoor(1, nmax+1, &X1, &Y1); screencoon(1, nmax+2, &X2, &Y2); screencoor(1, nmax+3, &X3, &Y3); for(i-nmax; i<nmax+4; i++) p[i].inuse-0; if (checksize && linside) { to_text(); printf ("Выходит за пределы (в функции plotaxes)\n"); printf("d-%f d-%f c2-%f\n", d, d, c2); printf("Xvp_min-%f Xvp_max-%f Yvp_min-%f Yvp_max-%f\n". Xvpmin, Xvp_max, Yvp_min, Yvp_max); for(cnt-nmax; cnt<nmax+4; cnt++) { pгintf(,,cnt-%dxe-%fye-%fze-%fX-%dY-%d\n,,, cnt, p[cnt].xe, p[cnt].ye, p[cnt].ze, IX(d*p[cnt].xe/p[cnt].ze+c1), IY(d*p[cnt].ye/p[cnt].ze+c2)); } exit(0); } lmove(X0, Y0); idraw(X1, Y1); text("x"); imove(X0, Y0); idraw(X2, Y2); text("y"); imove(X0, Y0); idraw(X3, Y3); text("z"); } static void plotpoint(int i, int Bold) { char str[30]; intX,Y,Xxy0,Yxy0,cnt; inside- 1; for(cnt-0; cnt<2; cnt++)
286 Приложение А. ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ { screencoor(1, i, &X, &Y); if(auxlines) { storepolnt(nmax+6, p[l].xw, p[l].yw, 0.0); screencoor(1, nmax+6, &XxyO, &YxyO); p[nmax+6].inuse-0; } if (inside) break; else { reposltlon();display(); } } if (cnt - - 2) ermes(ncnt - 2 в plotpoint*1); if (auxlines) draw_Jlne(XxyO, YxyO, X, Y); dot(X, Y); If (numbers) { sprlntf(str, w%d*\ i); lmove(X-2, Y-5); text(str); }else lf(nface--0ll Bold) { dot(X-1, Y-1); dot(X, Y-1); dot(X+1, Y-1); dot(X-1,Y); dot(X+1,Y); dot(X-1, Y+1); dot(X, Y+1); dot(X+1, Y+1); } static void plotsph(float rtio, float theta, float phi, int down) { float x, y, z, rcosphi, rsinphi, costh, slnth; theta-theta * Pldiv180; phi - phi * Pldlv180; rcosphi - rho * cos(phi); rsinphi - rho * sin(phi); costh - cos(theta); slnth - sin(theta); x-rsinphi * costh; у-rsinphi * slnth; z- rcosphi; if (down) dw(x, y, z); else mv(x, y, z); } static void points(void) { char str[50]; int i; float x, y, z; grmes(LIN2, "nxy z:"); getstr (margin, LIN3, str); If (sscanf(str, tt%d %f %f %Г, &i. &x, &y, &z)!-4 11 K-0) { grmes(LIN2, "Неверный формат числа"); polnts_entering - 0; return; } check_alloc(i); if (i >- nmax) nmax -1+1; if (storepoint(l, x, y, z)){checkall(); display();} plotpointO, 1); modified- 1;
ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 287 static void pressanykey(char *str) { grmes(LIN2,str); grmes(LIN3,"Нажмите клавишу..."); mygetchQ; } static char query(int line, char *s) > { Intpos; charch; grmes(line, s); pos - margin + 8 * strlen(s); textcursor(pos, line); ch - mygetchQ; s2[0]-ch; ch -toupper(ch); textXY(pos, line, s2); return ch; } static void rdfileQnt normal) { FILE*fp; Int ch, i_old-0, base, lowllmU, i, n, drawn, X, Y; float x, y, z, i_float; charstr[40]; modified - 0; /* Файл и структура данных идентичны! */ If (normal) { if(obJect_present) { ch - query(LIN2, "Очистить экран? (Y/N)"); if(ch--'Y' 11 ch--У)clear(); else modified -1; } if(kbhlt())getch(); grmes(LIN2, "Входной файл: "); y getstr(margin, LIN3, fllnam); > fp-fopen(filnam, Mrn); if (fp--NULL){grmes(LIN2, "Нельзя открыть файл"); return;} grmes(LIN4, "Пожалуйста, ждите..."); grmesCLINI,""); base - nmax - 1; inside -1; lowllmit - 32767; while (fscanf(fp, "%f %f %f %f\ &i_float, &x, &y, &z)—4) { if(getc(fp)!-'\n') { grmes(LIN2, "Неверный формат файла.*1); grmes(LIN3, "Задайте n x у z"); fclose(fp); return; } i-(intXLfloat + 0.001); lf(i<-0) { grmes(LIN3, "Номер не положительный"); fclose(fp); return; }
288 Приложение А. ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ i +- base; if (i < lowfimit) lowlimit — i; check_alloc(i); If (i >- nmax) nmax -1+1; storepoint(i, x. y, z); screencoonj), i, &Х, &Y); } if (base && iowlimit< 32767) { grmes(LIN1, "Диапазон новых точек:"); sprintf(str, M%d-%d", lowlimit, nmax- 1); pressanykey(str); } inside- 1; for(i-base; Knmax; I++) if (p[i].inuse)screencoor(1, i, &X, &Y); if(!inside)reposition(); do ch - getc(fp); while (isspace(ch)); axes - numbers *- (ch!- *F' && ch !- T); if(!axes)auxilnes-0; display(); if (!axes) /* To есть при наличии граней */ { do ch - getc(fp); while (ch !- An' && ch !- EOF); /* Пропуск входной строки с ключевым словом 'Faces' */ while (fscanf(fp, "%d", &i)- -1) { If ((pface[nface] - (int *)farcalloc(5, slzeof(int))) . --NULL) ermes("He хватает памяти"); n-0; while (1) { if (n>0){if (fscanf(fp, "%d", &i)<-0)break;} n++; drawn - i >- 0; i - abs(i) + base; if(p[i].inuse--0) { sprintf(str, "Неопр. точка: %6d", i); grmes(LIN3, str); fclose(fp); return; } if (n > 1 && drawn) line3(i_old,i); i_old-i; if(n>4) { if((pface[nface]-(int*) farrealloc(pface[nface], (n+1) * sizeof(int))) --NULL) ermesCHe хватает памяти"); pface[nfaceln]-(drawn ? i: -I); } ch - getc(fp); if(ch!-'#'&&ch!-'.') { grmes(LIN3, "Ожидается точка или #"); fclose(fp);
ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 289 return; } If (n > 2) { i-pface[nfaceI1]; if(i>-0)line3(i_old,i); } pface[nfaceIO]-n; nface++; if(nface--MAXFACES) егтезС'Слишком много граней"); } ch - getc(fp); }else numbers- 1; If (ch!-EOF)grmes(LIN2, "Неточный формат файла"); elsegrmes(LIN2,u"); grmes(LIN3,""); grmes(LIN4,M "); fclose(fp); > static Int rdnumber(lnt *p) /* Вызывается только из функций 'faces' и 'transform' */ { intl.neg.d, I0; charch; *p-0; do ch - getcMQ; while (isspace(ch)); neg-ch--'-'; lf(neg 11 ch--'+')ch-getch1(); If (llsdlglt(ch)) return 0; l-ch-'O'; while (ch-getch1(), Isdiglt(ch)) { d-ch-'O'; IO-l;l-10*l + d; If(KIO) { grmes(LIN3, "Слишком много цифр"); return 0; } } *p-(neg?-l:l); If (buf position > 0) buf position- -; return 1; } static Int rdoldnr(int *q) { int code, i, pnr; code - rdnumber(&Oi pnr - abs(l); *q -1; If (code --0 11 pnr>-nmax I I pnr &&p[pnr].inuse--0) { grmes(LIN2, "Неверный номер точки."); mygetch(); return 0; }else return 1; } static void reposition(void) /* Необходимо, чтобы значения xwmin и т.д. были верными */ Ю-273
290 Приложение А. ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ { float Xrange, Yrange, fx, fy, Xcenter, Ycenter, x1, y1, z1, q-0.4, xtol. ytol, ztol; intl.X.Y; x1 -0.5 * (xwmln + xwmax); y1 -0.5 * (ywmln + ywmax); z1 -0.5 * (zwmin + zwmax); xtol - q * (xwmax - xwmln); ytol - q * (ywmax - ywmln); ztol - q * (zwmax - zwmin); lf(fabs(xO-x1)>xtol II fabs(yO-y1)>ytol 11 fabs(zO-z1)>ztol) { grmes(LIN3, "Пожалуйста, ждите ...*); "* xO-x1;yO-y1;zO-z1; newcentralpolnt-1; /* См. команду 'E' */ fresh(); for(l-0; Knmax+6; i++) /* См. 'clear' и 'cursor' */ { lf(p[l].inuse) { storepolnt(l, p[l].xw, p{l].yw, p[l].zw); screencoor(0,1, &X, &Y); /* Вычисление новых значений xmln и др. */ if (axes) { storepolnt(nmax+6, p[i].xw, 0.0,0.0); screencoor(0, nmax+6, &Х, &Y); storepolnt(nmax+6,0.0, p[i].yw, 0.0); screencoor(0, nmax+6, &Х, &Y); storepolnt(nmax+6,0.0,0.0, p[l].zw); screencoor(0, nmax+6, &Х, &Y); } if(auxlines) { storepolnt(nmax+6, p[l].xw, p[l].yw, 0.0); screencoor(0, nmax+6, &X, &Y); } } } p[nmax+6].inuse - 0; grmes(LIN3,u "); grmes(LIN4,й "); } Xrange - xmax - xmln; Yrange - ymax - ymln; if (Xrange < 1e-12) Xrange - 1e-12; If (Yrange < 1e-12) Yrange -1e-12; Xcenter-0.5*(xmln+xmax); Ycenter-0.5*(ymln+ymax); fx - Xvp j-ange/Xrange; fy - Yvp^range/ Yrange; d-(fx<fy?fx:fy); if(!zoomln)d*-0.85; /* Резервируется место для новых точек */ с1 - Xvp_center-d*Xcenter; c2 -Yvp__center-d* Ycenter;
ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 291 static void screencoor(int mode, Int I, int *pX, lot *pY) { double xe. ye, ze, xs. ys; xe - p[l].xe; ye - p[i].ye; ze - p[l].ze; xs-xe/ze; ys-ye/ze; If (xs < xmln) xmln - xs; If (xs > xmax) xmax - xs; If (ys < ymln) ymin - ys; if (ys > у max) у max - ys; If (mode - - 0) return; xxx - d*xs+d; yyy - d*ys+c2; If(lzoomln) { lf(xxx<Xvp_min I I xxx>Xvp_max 11 yyy<Yvp_min 11 yyy >Yvp_max) Inside -0; } *pX-IX(xxx);*pY-IY(yyy); } static void show(char c) { lf(!zoomln) { s2[0]-c;textXY(margin + 140, LIN1,s2); } } static int storepoint(int i, float xw, float yw, float zw) { intoldpoint-O; double xe, ye, ze; if (Knmax && p[i].inuse) oldpoint- 1; p[i].xw - xw; p[i].yw - yw; p[i]zw - zw; if (p[i].inuse - - 0) p[i].inuse - 1; viewing(xw-xO, yw-yO, zw-zO, &xe, &ye, &ze); p[i].xe - xe; p[i].ye - ye; p[i].ze - ze; if (xw < xwmin) xwmin - xw; if (xw > xwmax) xwmax - xw; if (yw < ywmin) ywmin - yw; If (yw > ywmax) ywmax - yw; If (zw < zwmin) zwmin - zw;. If (zw > zwmax) zwmax - zw; if (ze < zemin) zemin - ze; if (ze > zemax) zemax - ze; if (i < nmax) objectpresent - 1; return oldpoint; /* Функция 'storepoint' обычно возвращает значение 0; значение 1 */ /* возвращается в том случае, если точка i уже существует */ } static void textcursor(int col, int line) { static char underlinep)*''^, dump}-" tt; intj; if (charavail) return; do 10**
292 Приложение А. ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ { J-100; dotextXY(col, line, underline); while (--j&&!kbhit()); J-200; do textXY(col, line, dum); while (- -j && !kbhit()); >while(!kbhit()); } static void transforrn(void) { charch, str[30]; int P—1, Q— 1, R—1, Pabs—1, Qabs—1, Rabs—1, A, B, Aabs, Babs, i, 11, duplicate, refK), tmp, *pnum,j,J1, n, k, kl.freepos, m, mabs, lowest, highest; double x, y,z, a, b.c.d, h, xP, xQ, xR, yP, yQ, yR, zP, zQ, zR, len, fact; float alpha-0.0, Sx-1.0, Sy-1.0, Sz-1.0, - xC,yC,zC,C1,C2,C3, xlowest-1.0, xhighest, x1-0.0, у 1-0.0, z1-0.0; while (nmax > 0 && p[nmax-1].inuse - - 0) nmax- -; grmes(LIN2, "Перенос (М)"); ch - query(LIN3, "или копия (С) ? "); If (ch - - 'С) grmes(LIN2, "Копия (С)"); duplicate-(ch--'С'); enquire(LIN3, "Нижняя граница: ", &xlowest); lowest - (IntXxlowest + 0.1); if (lowest < 1) lowest- 1; xhighest - nmax - 1; enquire(LIN4, "Верхняя граница:", &xhlghest); highest-(intXxhighest +0.1); if (highest >-nmax) highest-nmax- 1; if (duplicate) { freepos-nmax; check_alloc(nmax + highest - lowest); } grmes(LIN3,""); grmes(LIN4,"M); ch - query(LIN2, "Поворот? (Y/N)"); if(ch--'Y') { grmes(LIN1, "Поворот вокруг PQ.W); grmes(LIN2, "Номераточек Р и Q:")', bufposition--1; if(!rdoldnr(&P) 11 !rdoldnr(&Q)) return; Pabs - abs(P); Qabs - abs(Q); x - p[Pabs].xw; у - p[Pabs].yw; z - p[Pabs].zw; x1 - p[Qabs].xw; y1 - p[Qabs].yw; z1 - p[Qabs].zw; enquire(LIN2, "Угол в градусах:", &alpha); alpha-alpha* Pldiv180; initrotate(x, y, z, x1 - x, y1 - y, z1 - z, alpha);
ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 293 for(Howest; K-hlghest; I++) if (p[l].inuse &&(P >0 11 i!- Pabs) && (Q>0 I I 1!-Qabs)) { rotate(p[i].xw, p[i].yw, p[i].zw, &x, &y, &z); if (duplicate) { И -freepos++; p[i1].inuse-1; p[l].lnuse-11; }elsei1-i; p[i 1].xw - x; p[i 1].yw - y; p[i 1].zw - z; } }else { ch - query(LIN2, "Перенос? (Y/N)"); if(ch — 'Y') { grmes(LIN2," "); If (query(LIN1, "Вектор? (Y/N) "H-'Y') { enquire(LIN1, "deltax-M, &x1); enquire(LIN2, "deltay-", &y1); enquire(LIN3, "delta z-M, &z1); }else { grmes(LIN1, "AB - вектор переноса"); grmes(LIN2, "Номера точек А и В:"); bufposltion--1; if(!rdoldnK&A) 11 !rdoldnr(&B)) return; Aabs - abs(A); Babs - abs(B); x - p[Aabs].xw; у - p[Aabs].yw; z - p[Aabs].zw; x1-p[Babs].xw-x; y1-p[Babs].yw-y; z1-p[Babs].zw-z; } for(Howest; K-highest; i++) if(p[i].inuse&& (A>0 I I l!-Aabs)&&(B>OII I!-Babs)) { if (duplicate) { i1-freepos++;p[M].inuse-1;p[i].lnuse-i1; } else 11-I; p[H].xw-p[i].xw + x1; p[l 1 ].yw - p[i].yw + у 1; p[i1].zw-p[i].zw + z1; } }else { ch - query(LIN2, "Масштаб? (Y/N)"); if(ch--'Y') { if (query(LIN2, "Равномерный?(Y/N) M)--'Y') { enquire(LIN3, "Sx-Sy-Sz-", &Sx); Sy-Sz-Sx; }else { grmes(LIN2."и); enquire(LIN1,"Sx-w, &Sx); enquire(LIN2, "Sy-", &Sy); enquire(LIN3, "Sz-*\ &Sz); }
294 Приложение А. ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ while (1) { grmes(LIN1, "Фиксированная точка:"); grmes(LIN2. "Центр (С), Начало (О)"); ch-query(LIN3, "или Вершина (V): "); if(ch--'C') { checkall();xC-xO;yC-yO;zC-zO; }else if(ch--'0' II ch--'0') xC-yC-zC- 0.0; else If(ch--'V') { grmes(LIN2, "Номер точки: "); bufposltion--1; if (!rdoldnr(&P)) return; Pabs-abs(P); xC - p[Pabs].xw; yC - pfPabsj.yw; zC - p[Pabs].zw; } else continue; break; } C1-xC*(1-0-Sx); C2-yC*(1.0-Sy); C3-zC*(1.0-Sz); for(Howest; K-highest; I4+) lf(p[l].lnuse&&(P>0 II I!-Pabs)) { if (duplicate) { M-freepos-H-; p[H].inuse-1; p[l].lnuse-i1; }elsei1-i; p[M].xw-Sx*p[l].xw + C1; p[i 1].yw - Sy * pfij.yw + C2; p[i1].zw-Sz*p[i].zw+C3; } }else { ch-query(LIN2, "Отражение? (Y/N) "); if(ch--'Y') { grmes(LIN1, "PQR-плоск. отражения."); grmes(LIN2, "Номера точек P, Q, R:"); bufposltlon - -1; refl - 1; if (!rdoldnr(&P) 11 !rdoldnr(&Q) 11 !rdoldnr(&R)) return; Pabs - abs(P); Qabs - abs(Q); Rabs - abs(R); xP - p[Pabs].xw; yP - p[Pabs].yw; zP - p[Pabs].zw; xQ - p[Qabs].xw; yQ-- p[Qabs].yw; zQ - p[Qabs].zw; xR - p[Rabs].xw; yR - p[Rabs].yw; zR - p[Rabs].zw; a - yP*(zQ-zR) + yQ*(zR-zP) + yR*(zP-zQ); b - xP*(zR-zQ) + xQ*(zP-zR) + xR*(zQ-zP); с - xP*(yQ-yR) + xQ*(yR-yP) + xR*(yP-yQ); len - sqrt(a*a+b*b+c*c);
ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 295 if (len - - 0.0) len - 1е-15; а /- len; b /- len; с /- len; d-a*xP + b*yP + c*zP; for(i-lowest; K-highest; I++) if(p[i].inuse&& (P>0 11 i!-Pabs)&& (Q >0 11 i!-Qabs)&& (R > 0 11 ll-Rabs)) { if (duplicate) { 11 - f reepos++; p[M].inuse-1;p[i].inuse-l1; }elsei1-i; x - p[i].xw; у - p[i]yw; z ~ p[i].zw; h * a*x +b*y + c*z; fact - 2.0 * (d-h); p[i1].xw-x + fact*a; p[M].yw-y + fact*b; p[i1].zw-z + fact*c; } }else{grmes(LIN2,""); return;} } } } if (duplicate) { grmes(LIN1, "Диапазон новых точек:"); sprintf(str, M%d-%d", nmax, freepos-1); pressanykey(str); nmax-free pos; } grmes(LIN3, "Пожалуйста, ждите..."); modified- 1; checkall(); while (nface >0 && pface[nface-1]- - NULL) nface- -; if (duplicate) { lf(2*nface>-MAXFACES) { grmes(LIN3, "Слишком много граней"); getch(); display(); return; } freepos - nface; forQ-0;j<nface;J-H-) {. if(pface[J]-- NULL) continue; j 1 - f reepos-н-; pface[j1]-NULL; n - pface[JlO]; If (n<0) ermes("n<0 в функции transform"); If ((pface[J1]-(lnt *)farcalloc(n+1, slzeof(lnt))) --NULL) • ermes("He хватает памяти"); pface[J1I0]-n; for(k-1;k<-n;k++)
296 Приложение А. ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ { к1 -(refl ? (n >3? (k<4? 4-k: 4+п-к) : n+1-k) :к); m - pfaceUJk 1 ]; mabs - abs(m); if (mabs < lowest 11 mabs > highest) break; pface[J1Ik]-(mabs--Pabs&&P<0 11 mabs--Qabs&&Q<0 11 mabs--Rabs&&R<0 ? m : (m < 0 ? -p[mabs].inuse : p[m].inuse)); } if (mabs < lowest 11 mabs > highest) { farf ree(pface[J 1]); freepos- -; } } nface - freepos; }else if (refl) /* Инвертирование ориентации всех граней */ { for(|-0;J<nface;J-H-) { lf(pface[j]-- NULL) continue; n - pfacefjIO]; lf(n<3) continue; pnum - pface[j]; tmp-pnum[1]; pnum[1]-pnum[3];pnum[3]-tmp; k1-(n-3)/2; for(k-1;k<-k1;k-H-) { tmp-pnum[3+k]; pnum[3+k] - pnum[n+1-k]; pnum[n+1-k]-tmp; } /* Например,старая: 12 345678. */ } /* новая:32187654. */ } /* (Любые вершины, кроме 2, могут быть вогнутыми) */ display(); } static void vfewing(float x, float y, float z, double *pxe, double *pye, double *pze) { *pxe-v11*x + v21*y; *pye-v12*x + v22*y + v32*z; *pze - v13*x + v23*y + v33*z + v43; lf(*pze<-EPS) ermes ("Пож. задайте значение для rho больше"); } static void wrfile(void) { IntiJ.n, k; charch; FILE*fp; grmes(LIN2, "Выходной файл: "); if(kbhit())getch();
ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 297 If (*filnam) grmes(LIN3, filnam); textcursor(margin + 8*strlen(filnam)+-8, LIN3); If (ch - mygetch(), ch !- '\n* && ch !- '\r') { ungetch(ch); getstr(margln, LIN3, filnam); } if(*filnam",\0' 11 (fp-fopen(fllnam, "w"))--NULL) { grmes(LIN3, "Нельзя открыть файл"); return; } for(H);i<nmax;i++) if(p[l].inuse) fprlntf(fp,M%d%f%f%An'\ i, p[i].xw, p[l].yw, p[l].zw); lf(nface>0) { fprlntf(fp, "Faces- грани:\пи); forO-0;J<nface;J-H-) { If (pfaceU]- - NULL) continue; n - pface[JlO]; If (n < 0) ermes("n<0 в функции wrflle"); for(k-1; k<-n; k++) fprlntf(fp,tt %d", pface[Jlk]); fprlntf(fp,M.\nM); } } grmes(LIN2," "); grmes(LIN3, u "); grmes(LIN4. u "); fclose(fp); modified - 0; /* Файл и структура данных идентичны */ } static void zoom(lnt code) /* zoom может быть вызвана после inltgr */ { if (code) /* 1-large; 0-small */ { Xvp_min - 0.25; Xvpmax - x_max - 0.25; Yvp_min - 0.4; Yvpmax - y_max - 0.4; }else { Xvp_min-0.25; Xvp_max-6.3; /* Резервирование места для команд */ Yvpmin - 0.4; Yvpmax - 6.7; /* zoom(0) не будет вызываться при pridim - 1 */ /* поэтому имеем хтах - 10.0, у_тах - 7.0 */ } zoomln-code; Xvp_range " Xvpmax - Xvpmln; Yvp_range - Yvp_max - Yvp_min; Xvp_center-0.5*(Xvp_mln + Xvp_max); Yvp_center-0.5*(Yvp_min + Yvpmax);
Приложение Б ФУНКЦИЯ УДАЛЕНИЯ НЕВИДИМЫХ ЛИНИЙ ( Исходный текст модуля HLPFUN ) /* HLPFUN: Функция для удаления невидимых линий с использо- */ /* ванием приборно-независимых пикселей. Эта версия содержит */ /* средства для обработки кривых поверхностей. */ #include<math.h> #include<ctype.h> ♦Include <alloc.h> ♦Include <conio.h> #lnclude<string.h> #include <process.h> ♦Include "grpack.h" #define max(x.y) ((x)%)?(x):(y)) ♦define min(x.y) ((x)<(y)?(x):(y)) #deflne max3(x,y,z) ((x)>(y)?max(x,z):max(y,z)) #deflne mln3(x,y,z) ((xHy)?mln(x,z):mln(y,z)) #deflne xwhole(x) ((lntX((xhxmln)/deltaX)) #define ywhole(y) ((int)(((y)-ymin)/deltaY)) ♦define xreal(i) (xmln+((l))*deltaX) ♦define M-1000000.0 ♦define NSCREEN 15 ♦define BIG 1.e30 int abs(int I); static void check_kbhlt(vold), addjlnesegment(lnt P, Int Q), viewing(double x, double y, double z, double *pxe, double *pye, double *pze), llnesegment ( double xP, double yP, double zP, double xQ, double yQ, double zQ, Int k);
Исходный текст модуля HLPFUN 299 void ermes(char *str), /* См. также модуль D3D */ coeff(float rho, float theta, float phi), Inltviewport(vold); static Int counter_clock(lnt 10, IntM, |ntl2, Int code), lncludesvertex(lnt h, Int I, Int J), sameside (double xj, double yj, double xl, double yl, double xh, double yh, double xl, double yl), allocsize(int dlvfactor, int elsize); extern int nmax, nface, **pface; extern float xO, yO, zO, xmin, xmax, ymin, ymax; static int iplxmin, ipixmax, ipixleft, ipixright, Ipix, jplx, Jtop, Jbot, J_old, jl, topcode[3], *POLY, npolyalloc, *vertexconvex, ntrsetalloc, npoly, lslze-slzeof(lnt), LOWER[NSCREEN], UPPER[NSCREEN]. LOWfNSCREEN], UP[NSCREEN], ntrset, maxvertex, Jface, nTRIANGLE, nTRLIST, ITRIANGLE, ITRLIST, Inode, itria, *trset; extern double v11, v12, v13, v21, v22, v23, v32, v33, v43, Pldiv180; static double d, d, c2, eps-1e-5, meps—1e-5, oneplus-1+1.e-5, Xrange, Yrange, Xvprange, Yvprange, deltaX, deltaY, denom, slope, Xleft[3], Xright[3], Yleft[3], Yrlght[3], a, b, c, surfl; struct linsegface { int i; double a/ b, c; struct linsegface *next; } *lsegfacenode(vold); struct vertex { intinuse; float xw, yw, zw; double xe, ye, ze; struct linsegface *connect; }*VERTEX,*pvertex; extern struct vertex *p; static struct triangle { IntA.B.C; double a, b, c, h; } *TRIANGLE, *ptrlangle; static struct Iseglist { double xP, yP, zP, xQ. yQ, zQ; intk; struct Iseglist *next; }*lsegstart, *auxlseg, *lsegnode(void);
300 Приложение Б. ФУНКЦИЯ УДАЛЕНИЯ НЕВИДИМЫХ ЛИНИЙ struct trlist { unsigned int ptria; unsigned int next; } *TRLIST, *pnode; struct { inttrcov; float tr_dist; unsigned int start; }SCREEN[NSCREENINSCREEN], 'pointer; void hlpfun(float rho, float theta, float phi, float surflimit) { int P, Q, ii, vertexnr, i, kk, Ю, i 1 !2, code, count, polygonconvex, loopcount, jtr, J, n, k; struct linsegface *ptr, *dummy, *dummyO; double Xvp_min4), Xvpmax, Yvpmin-Ю, Yvp_max, fx, fy, Xcentre, Ycentre, Xvpcentre, Yvpcentre, xP, yP, zP, xQ, yQ, zQ, XP, YP, XQ, YQ, Xlft, Xrght, Ylft, Yrght, xxP, yyP, zzP, xxQ, yyQ, zzQ; textXY(40,40, /* "Please wait..." */ "Пожалуйста, ждите..."); Xvp_max - xmax; Yvpmax - ymax; surf I-(surf limit >.999? 1.1: surflimit); npolyalloc-200; POLY-{int *)farcalloc((long)npolyalloc, (long)isize); vertexconvex-(lnt *)farcalloc((long)npolyalloc, (long)isize); if (POLY--NULL I I vertexconvex--NULL) { ermes( /* "Memory: polygon" */ "Память: полигон"); } VERTEX-p; /* Теперь VERTEX указывает на ту же область */ /* памяти, как указатель 'р' в модуле D3D */ /* Инициализация экранной матрицы */ for(ipix-0; lpix<NSCREEN; ipix++) forOplx-0;Jpix<NSCREEN;jpix++) { pointer-&(SCREEN[lplxlJpixD; pointer->tr_cov—1; pointer->tr_dist-BIG; pointer->start—1; } coeff(rho, theta, phi); maxvertex - nmax-1; /* Значение nmax определено в модуле D3D */ dummy - lsegfacenode(); for (i—1; i<-maxvertex; i++) VERTEX[l].connect-(VERTEX[i].inuse? NULL: dummy); /* dummy означает : не используется */ /* NULL означеет: список пуст (но в работе) */
Исходный текст модуля HLPFUN 301 /* Вычисление экранных координат */. Xrange-xmax-xmin; Yrange-ymax-ymin; Xvprange-Xvpmax-Xvpmin; Yvprange-Yvpmax-Yvpmin; fx-Xvp_range/Xrange;fy=Yvp_range/Yrange; d-(fx<fy?fx:fy); Xcentre-0.5*(xmin+xmax); Ycentre4).5*(ymin+ymax); Xvp_centre-0.5*(Xvp_min+Xvp_max); Yvp_centre-0.5*(Yvp_min+Yvp_max); d-Xvp_centre-d*Xcentre;c2-Yvp_centre-d*Ycentre; deltaX-oneplus*Xrange/NSCREEN; delta Y-oneplus*Yrange/NSCREEN; /♦Теперьимеем: Xrange/deltaX<NSCREEN */ iTRLIST - 0; ITRIANGLE -0; nTRLIST-allocsize(4, sizeof(struct trlist)); TRLIST-(struct trlist *)farcalloc((long)nTRLIST, (long)slzeof(struct trlist)); nTRIANGLE -allocslze(3, slzeof(struct triangle)); TRIANGLE -(struct triangle *)farcalloc((long)nTRIANGLE, (long)sizeof(struct triangle)); If (TRLIST--NULL 11 TRIANGLE--NULL) ermes( "Память: треугольники"); for(j-0;J<nface;J++) { If (pface[J] - - NULL) continue; n - pface[JlO]; lf(n<0)ermes( "Внутренняя ошибка: n<0"); if (n - -1) ermes("Полигон только с одной вершиной"); POLY[0]-pfacefJl1]; npoly-1; for(k-2;k<-n;k++) { i-pfaceUIk]; if (npoly- -npolyalloc) { npolyalloc-fr-(npolyalloc»2); POLY-(int*) farrealloc(POLY,(long)npolyalloc*lsize); vertexconvex - (int *) farrealloc(vertexconvex,(long)npolyalloc*isize); if (POLY--NULL 11 vertexconvex--NULL) ermes( "Память: полигоны"); } POLY[npoly++]-i; } if (npoly- -1) ermes( "Внутреняя ошибка: npoly- Г); If (npoly--2) { add_linesegment(POLY[0], POLY[1]); continue; } Jface-j; if (!counter_clock(0,1,2,0)) continue;
302 Приложение Б. ФУНКЦИЯ УДАЛЕНИЯ НЕВИДИМЫХ ЛИНИЙ /* стирание символа */ for (i—1; i<—npoly; i++) { IH%npoly; code-POLY[il]; vertexnr-abs(code); if (code<0) POLY[ii]-vertexnr; else addJinesegmentfPOLYp-l], vertexnr); } /* Разбиение полигона на треугольники */ count-1; polygonconvex-O; 11—1; while (npoly>2) { if(!polygonconvex) { polygonconvex-1; for (il-0; IKnpoly; li-м-) { Ю - (ii- -0 ? npoly-1: ii-1); i2-(ii«-npoly-1?0:IH-1); vertexconvex[ii]-counter_clock(iO, ii, i2,0); If (!vertexconvex[iiJJ polygonconvex-O; } } loopcount-npoly; do { if (-M-11 >- npoly) i 1-0; I0-(I1--0? npoly-1 :i 1-1); 12 - (i 1—npoly-1 ? 0: i 1+1); } while (--loopcount && Ipolygonconvex && (!vertexconvex[i1] 11 includesvertex(IO, 11,12))); /* Запоминание треугольников: */ counter_clock(iO, И, 12, count++); npoly- —, for(iH1; IKnpoly; II++) POLY[ii]-POLY[ll+1]; } } /* Добавление ближайшего треугольника к экранному списку */ for(lpix-0; ipix<NSCREE.N; iplx-н-) for(]pix-0; jpix<NSCREEN; jpix-н-) { pointer-&(SCREEN[ipixlJpix]); if (pointer->tr_cov >- 0) { pnode - TRUST+ITRUST; pnode->ptrla - pointer->tr_cov; pnode->next - polnter->start; pointer->start-iTRLIST; lf(++ITRLIST--nTRLIST) ermes( /* "Memory: Triangle list" V "Память: Список треугольников"); } } farfree(POLY);farfree(vertexconvex); ntrsetalloc-1000; trset-(lnt*) farcalloc(ntrsetalloc, slzeof(unsigned Int));
Исходный текст модуля HLPFUN 303 If (trset - - NULL) ermes( "Память: набор треугольников"); Isegstart-NULL; clearpageQ; lnit_viewport(); /* Вычерчивание всех отрезков при их видимости */ for(P-1; P<-maxvertex; Р++) { pvertex-VERTEX+P; /* - &VERTEX[P] */ ptr- pvertex->connect; if (ptr - - dummy II ptr - - NULL) continue; xP - pvertex->xe; yP - pvertex->ye; zP - pvertex->ze; XP-xP/zP;YP-yP/zP; for(; ptr!- NULL; ptr- ptr->next) { Q-ptr->l; pvertex-VERTEX+Q; /* - &VERTEX[Q] */ xQ - pvertex->xe; yQ - pvertex->ye; zQ - pvertex->ze; XQ-xQ/zQ; YOyQ/zQ; /* Используя экранные списки, построим набор треуголь- */ /* ников, которые могут закрывать точки отрезка PQ */ If (XP<XQ I I (XP--XQ && YP<YQ)) {Xlft-XP; Ylft-YP; Xrght-XQ; Yrght-YQ; }else {Xlft-XQ; Ylft-YQ; Xrght-XP; Yrght-YP; } lplxleft-xwhole(Xlft);lplxrJght-xwhole(Xrght); ч. denom-Xrght-XIft; If (Iplxleft!- iplxrlght) slope-(Yrght-Ylft)/denom; jbot-Jtop-ywhole(Ylft); for (ipix—Ipixleft; ipix<—ipixright; Ipix-H») { if (ipix— -ipixright) jl-ywhole(Yrght); else jl-ywhole(Ylft-Kxreal(ipix+1)-Xlft)*slope); LOWER[ipixJ-minQbot,JI); jbot-JI; UPPER[lpix]-maxOtop,jl); jtop-jl; } ntrset-0; for(lplx-ipixleft; ipix<-lpixright; ipix-н-) for (jplx-LOWER[ipix]; Jpix<-UPPER[lpix]; Jpix-н-) { polnter-&(SCREEN[lpixBpix]); inode- pointer->start; while (inode>-0) /* В конце списка будем иметь inode— t/ { pnode - TRLIST + Inode; itria - pnode->ptrla; /* Треугольники будут записаны в память только, */ /* если они еще не находятся в массиве trset */ /* (массив треугольников). */ if (ntrset- -ntrsetalloc) { ntrsetalloc -н- (ntrsetalloc » 2); trset -(int*) farrealloc( trset, (long)ntrsetalloc*sizeof(unsigned int)); if (trset--NULL) ermes ("Память: набор треугольников");
304 Приложение Б. ФУНКЦИЯ УДАЛЕНИЯ НЕВИДИМЫХ ЛИНИЙ } trset[ntrsetHtria; /* страж */ jtr-0; while (trset[jtr]Htria) jtr++; ifQtr--ntrset) { ntrset++; /* Запоминание треугольника */ > inode-pnode->next; } } /* Теперь trset[0] trset[ntrset-1] перечень */ /* треугольников, которые могут закрывать точки PQ. */ linesegment(xP, yP, zP, xQ, yQ, zQ, 0); while (Isegstart!- NULL) { xxP-lsegstart->xP; yyP-lsegstart->yP; zzP-lsegstart->zP; xxQ4segstart->xQ; yyOlsegstart->yQ; zzQ-l seg sta rt->zQ; kk-lsegstart->k; auxlseg - Isegstart; Isegstart - lsegstart->next; farfree(auxlseg); linesegment(xxP, yyP, zzP, xxQ, yyQ, zzQ, kk); } } } farfree(dummy); farfree(trset); for (1-1; i<-maxvertex; i++) if(VERTEX[i].inuse) { dummy-VERTEX[l].connect; while (dummy!-NULL) { dummyO-dummy; dummy-dummy->next; farfree(dummyO); } } farfreefTRLIST); farfreefTRIANGLE); } void coeff(float rho, float theta, float phi) { double th, ph, costh, sinth, cosph, sinph; /* Углы в радианах */ th-theta*Pldiv180;ph-phi*Pldiv180; costh-cos(th); si nth-si n(th); cosph-cos(ph); sinph-sln(ph); /♦Элементы видовой матрицы V */ v11—sinth; v12—cosph*costh; v13—sinph*costh; v21-costh; v22—cosph*sinth; v23—sinph*sinth;
Исходный текст модуля HLPFUN 305 v32-sinph; v33—cosph; v43-rho; static void add_linesegment(int P, int Q) /* Эта функция может вставить отрезок прямой PQ в связанный */ /* список, начинающийся с VERTEX[P].connect (при необходи- */ /* мости можно поменять местами значения Р и Q так, чтобы Р */ /* всегда была меньшей из двух). Если отрезок PQ уже был */ /* записан в список ранее, то он не повторяется. Если обна- */ /* ружен ранее записанный отрезок PQ, то производится срав- */ /* нение грани, которой он принадлежит, с гранью, которой */ /* принадлежит новый отрезок. Если векторы нормалей этих */ /* двух граней почти параллельны (скалярное произведение их */ /* векторов нормалей больше некоторого 'предельного' значе- */ /* ния), то старый отрезок прямой удаляется. */ { struct linsegface *ptr, *new, **pp, **pp0; intiaux; if (РХЭ) { iaux-P; P-Q; Q-laux; } /* Now: P < Q */ /* Теперь: P < Q */ ppO-pp-&(VERTEX[P].connect); ptr-*pp; while (ptr!- NULL && ptr->i!- Q) { pp- &(ptr->next); ptr- *pp; } if (ptr--NULL) { new-lsegfacenode(); new->i - Q; new->a - a; new->b - b; new->c - c; new->next - *ppO; *ppO - new; }else if (a*(ptr->a) + b*(ptr->b) + c*(ptr->c) > surf I) { *pp - ptr->next; farf ree(ptr); } } static Int counter_clock(int Ю, int 11, Int 12, int code) /* code - 0: вычисление ориентации; */ /* code- 1: вычисление a, b, c, h; */ /* запоминание первого треугольника; */ /* code > 1: проверка следующего треугольника на копланар- */ /* ность, запоминание его */ { Int A-POLY[i0], B-POLY[i1], C-POLY[i2], i, I; double xA, yA, zA, xB, yB, zB, xC, yC, zC, r, XA,YA,XB,YB,XC,YC,hO, DA, DB, DC, D, DAB, DAC, DBC, aux, dist, xR.yR, dev, tol; static double h;
306 Приложение Б. ФУНКЦИЯ УДАЛЕНИЯ НЕВИДИМЫХ ЛИНИЙ if(A<0)A—A; if(B<0)B—В; if(C<0)C—С; pvertex-VERTEX+A; хА - pvertex->xe; уА - pvertex->ye; zA - pvertex->ze; pvertex-VERTEX+B; xB - pvertex->xe; yB - pvertex->ye; zB - pvertex->ze; pvertex-VERTEX+C; xC - pvertex->xe; yC - pvertex->ye; zC - pvertex->ze; hO-xA*(yB*zC~yC*zB)~ xB*(yA*zC-yC*zA) + xC*(yA*zB-yB*zA); If (code--0) { lf(h0<1e-9) return 0; /* Corrected */ /♦Исправлено */ a - yA * (zB-zC) - yB * (zA-zC) + yC * (zA-zB); b - -(xA * (zB-zC) - xB * (zA-zC) + xC * (zA-zB)); с - xA * (yB-yC) - xB * (yA-yC) + xC * (yA-yВ); r-sqrt(a*a+b*b+c*c); lf(r--O.0)r-1e-9; /* Corrected */ /*Исправлено */ a - a/r; b - b/г; с - c/r; h - hO/r; return 1; } /* Если hO-0, то плоскость ABC проходит через точку Ей */ /* ничего не закрывает */ /* Если h0<0, то треугольник задний. V /* В обоих случаях переменная ITRIANGLE не получает - */ /* приращения и треугольники в полигоне не запоминаются. */ /* Переменные а, Ь, с будут использованы в add Jlnesegment. */ dev-fabs(a*xC+b*yC+c*zC-h);tol-eps-HD.001*fabs(h); if (dev>tol) { tojextQ; printf( /* "Vertices not In the same plane:" */ "Вершины не в одной плоскости:\nu); for (1-1; K-pface[jfacelO]; |++) prlntf("%du, pfacepfacell]); exit(1); } ptrlangle - TRIANGLE + ITRIANGLE; ptriangle->A - A; ptrlangle~>B - В ; ptriangle->C - C; ptrlangle~>a - a; ptrlangle->b - b ; ptrlangle->c - c; ptrlangle->h-h; /* Теперь треугольник будет записан в экранные списки */ /* ассоциированных пикселей; */ /* сначала определяются массивы LOWER, UPPER, LOW, UP */
Исходный текст модуля HLPFUN 307 XA-xA/zA; YA-yA/zA; XB-xB/zB;YB-yB/zB; XC-xC/zC; YC-yC/zC; DA-XB*YC-XC*YB; DB-XC*YA-XA*YC; DC-XA*YB-XB*YA; D-DA+DB+DC; DAB-DC-M*(XA-XB); DAODB-M*(XC-XA); DBC-DA-M*(XB-XC); topcode[OHD*DAB>0); topcode[1]-(D*DAC>0); topcode[2HD*DBC>0); Xleft[0}-XA; Yleft[0]-YA; Xright[0}-XB; Yright[0}-YB; Xleft[1]-XA; Yleft[1]-YA; Xright[1]-XC; Yright[1]-YC; Xleft[2}-XB; Yleft[2}-YB; Xright[2}-XC; Yright(2}-YC; for (M); КЗ; I++) /* I - номер стороны треугольника */ if (Xleft[l]>Xright[l] 11 (Xleft[l]—Xright[l] && Yleft[l]>Yright[l])) { aux-Xleft[l]; Xleftj;i]-Xright[l]; Xright[l]-aux; aux-Yleft[l]; Yleft[l]-Yright(l]. Yrigh^l]-aux; } Ipixmin-xwholetminatXA.XB.XC)); ipixmax-xwhole(max3(XA,XB,XC)); for(ipix-ipixmln; ipix<-lpixmax; ipix-н-) { LOWER[ipix]-UP[ipix}-10000; UPPER[iplxKOW[ipixh-10000; } for(l-0;K3;l-H-) { ipixleft-xwhole(Xleft[l]);ipixright-xwhole(Xright[l]); denom-Xhght[l]-Xleft[l]; if (ipixleft!- iplxright) slope-(Yright[l]-Yleft[l])/denom; J_old-ywhole(Yleft[l]); for(iplx-iplxleft; ipix<—Ipixright; ipix-н-) { if (ipix- -ipixright) JI-ywhole(Yright[l]); else jl-ywhole(Yleft[l}+<xreal(ipix+1)-Xleft[l])*slope); if(topcode[l]) { UPPER[lplx]-max3G_old,JllUPPER[ipix]); UP[ipix]-min30_old.JI,UP[ipix]); }else { LOWERfipixj-minaO.old.jl.LOWERflplx]); LOW[ipix]-max30_oldJI.LOW[ipix]); } j_old-JI; } } /* Для столбца ipix на экране треугольник ассоциируется */ /* только с пикселями в строках */ /* LOWER[ipix] UPPER[ipix] */ /* Поддиапазон LOW[ipix}+1 UP[ipix]-1 этих строк */ /* указывает на пиксели, которые полностью лежат внутри */ /* данного треугольника */
308 Приложение Б. ФУНКЦИЯ УДАЛЕНИЯ НЕВИДИМЫХ ЛИНИЙ for ( iplx-ipixmln; ipix<-iplxmax; ipix++) for ( Jplx-LOWER[ipix]; jpix<-UPPER[ipix]; Jpix++) { polnter-&(SCREEN[iplxlJpix]); if Qplx>LOW[ipix] && Jpix<UP[ipix]) { xR-xmin-Kipix-K).5)*deltaX; yR-ymln-Kjplx-K).5)*deltaY; denom-a*xR+b*yR+c*d; ^ dlst- fabs(denom)>eps? h*sqrt(xR*xR+yR*yR+1.)/denom: BIG; /* Прямая линия из точки наблюдения Е в точку пикселя */ /* (xR, yR, 1) пересекает плоскость ABC на расстоянии */ /* 'dist' отточки Е */ If (dist< pointer->tr_dlst) { pointer->tr_co\HTRIANGLE; polnter->tr_dist-dlst; } }else /* Добавление треугольника к экранному списку */ { pnode- TRUST + ITRLIST; pnode->ptria- ITRI ANGLE; pnode->next- polnter->start; polnter->start- iTRLIST; if(++ITRLIST--nTRLIST) ermes( "Память: Список треугольников"); } } If (++ITRIANGLE - - nTRIANGLE) ermes( "Память: Треугольники"); return 1; } static void vlewing(double x, double y, double z, double *pxe, double *pye, double *pze) { *pxe-v11*x + v21*y; *pye - v12*x + v22*y + v32*z; *pze-v13*x + v23*y + v33*z + v43; } static void linesegment(double xP, double yP, double zP, double xQ, double yQ, double zQ, Int k) /* Отрезок прямой PQ должен быть вычерчен, если только */ /* он не закрывается треугольниками, записанными в */ /* элементы массива с trset[k] до trset[ntrset-1]. */ { Int А, В, С, i, Pbeyond, Qbeyond, outside, Poutside, Qoutside, eA, eB, eC, sum; double a, b, c, h, hP, hQ, r1, r2, r3, xA, yA, zA, xB. yB, zB, xC, yC, zC, dA, dB, dC, labmin, labmax, lab, mu, xmin, ymin, zmin, xmax, ymax, zmax, C1, C2, C3. K1, K2, КЗ, denoml, denom2, Cpos, Ppos, Qpos, aux, epsl;
Исходный текст модуля HLPFUN 309 struct triangle *ptriangle; while (k<ntrset) { itria-trset[k]; ptriangle-TRIANGLE+ltria; a-ptriangle->a; b-ptriangle->b; c-ptriangle->c; h-ptriangle->h; /*Тест1*/ hP-a*xP+b*yP+c*zP;hQ-a*xQ+b*yQ+c*zQ; eps1-eps+eps*h; if (hP-h<-eps1 && hQ-h<-eps1){k-H-; continue;} /* Отрезок PQ не позади плоскости ABC */ /*Тест2*/ K1-yP*zQ-yQ*zP; K2-zP*xQ-zQ*xP; K3-xP*yQ-xQ*yP; A-ptriangle->A; B-ptriangle->B; C-ptriangle->C; pvertex-VERTEX+A; xA - pvertex->xe; у A - pvertex->ye; zA - pvertex->ze; pvertex-VERTEX+B; xB - pvertex->xe; у В - pvertex->ye; zB - pvertex->ze; pvertex-VERTEX+C; xC - pvertex->xe; yC - pvertex->ye; zC - pvertex->ze; d A-K1 *x A+K2*yA+K3*zA; dB-K1*xB+K2*yB+K3*zB; dC-K1*xC+K2*yC+K3*zC; /* Если dA, dB, dC имеют одинаковый знак, то вер- */ /* шины А, В, С расположены по одну сторону от */ /* плоскости EPQ */ еА- d A>eps ? 1: dA<meps ? -1:0; еВ- dB>eps ? 1: dB<meps ? -1 :0; eC- dOeps ? 1 : dC<meps ? -1 :0; sum-eA+eB+eC; if (abs(sum)>^2) { k++; continue; } /* Если этот тест выполнен успешно, то бесконечная */ /* прямая линия PQ лежит вне пирамиды ЕАВС(или */ /* линия и пирамида имеют по крайней мере одну об- V /* щую точку). Если тест не выполнен, то имеется */ /*точка пересечения */ /*ТестЗ*/ Poutside-QoutsideK); labmin-1.; Iabmax4).; for(M);l<3;!++) { C1«yA*zB-yB*zA; C2-zA*xB-zB*xA; C3-xA*yB-xB*yA; /*C1x + C2y + C3z-0 уравнение плоскости Е АВ */ Cpos-C 1 *xC+C2*yC+C3*zC; Ppos-C1*xP+C2*yP+C3*zP; Qpos-C 1 *xQ+C2*yQ+C3*zQ; denom1HDpos-Ppos;
310 Приложение Б. ФУНКЦИЯ УДАЛЕНИЯ НЕВИДИМЫХ ЛИНИЙ if(Cpos>eps) { Pbeyond- Ppos<meps; Qbeyond- Qpos<meps; outside- Pbeyond && Qpos<-eps 11 Qbeyond && Ppos<-eps; }elseif(Cpos<meps) { Pbeyond- Ppos>eps; Qbeyond- Qpos>eps; outside-Pbeyond &&Qpos>-meps I I Qbeyond && Ppos>-meps; }elseoutslde-1; if (outside) break; lab-fabs(denom1)<-eps ? 1.e7:-Ppos/denom1; /* Переменная lab показывает, где прямая PQ/ /* встречается с плоскостью ЕАВ. */ Poutside I-Pbeyond; Qoutside I-Qbeyond; denom2-dB-dA; mu- fabs(denom2)<-eps ? 1 .e7: -dA/denom2; /* Переменная mu указывает, где прямая АВ */ /* встречается с плоскостью EPQ. */ if (mu>-meps && mu<-oneplus && lab>-meps && lab<-oneplus) { if (lab<labmin) labmin-lab; if (labXabmax) labmax-lab; } aux-xA; xA-xB; xB-xC; xC-aux; aux-yA; yA-yB; yB-yC; yC-aux; aux-zA; zA-zB; zB-zC; zC-aux; aux-dA; dA-dB; dB-dC; dC-aux; } If (outside) {k++; continue;} /*Тест4*/ if (!(Poutside 11 Qoutside)) return; /* Прямая PQ невидима */ /*Тест5*/ M-xQ-xP; r2-yQ-yP; r3-zQ-zP; xmin-xP+labmin*r1; ymin-yP+labmln*r2; zmln-zP+labmln*r3; If (a*xmin+b*ymln+c*zmln-h<-eps1){ k++; continue; } xmax-xP+labmax*r1; ymax-yP+labmax*r2; zmax-zP+labmax*r3; if (a*xmax+b*ymax+c*zmax-h<-eps1){ k++; continue; } /* Если этот тест выполнен, то точка пересечения */ /* прямой PQ и пирамиды лежит перед плоскостью ABC */
Исходный текст модуля HLPFUN 311 Л Тест 6*/ if (Poutside 11 hP<h-eps1) { auxlseg-lsegstart; Isegstart - lsegnode(); lsegstart->xP«-xP; lsegstart->yP-yP; ^ lsegstart->zP-zP; lsegstart->xQ-xmin; lsegstart->yQ-ymln; lsegstart->zQ - zmin; lsegstart->k- k+1; lsegstart->next-auxlseg; } if (Qoutslde 11 hCKh-eps1) { auxlseg-lsegstart; Isegstart - lsegnode(); lsegstart->xP - xmax; lsegstart->yP - ymax; lsegstart->zP - zmax; lsegstart->xQ - xQ; lsegstart->yQ - yQ; lsegstart->zQ - zQ; lsegstart->k - k+1; lsegstart->next - auxlseg; } return; } move{d*xP/zP+c1, d*yP/zP+c2); draw(d*xQ/zQ+c1, d*yQ/zQ+c2); } void init_ylewport(void) { float len-ОД len 1-0.2; move(0A len); draw(0.0,0.0); drawflen, 0.0); move(x_max4en, 0.0); draw(x_max, 0.0); draw(x_max, len); move(x_max, ymaxHenl); draw(xjnax, y_max); draw(x_max-len1, y_max); move(len1, ymax); draw(0.0, y_max); draw(0.0, y_max-len1); /* Разница в значениях переменых len и Ien1 /* позволяет отличить верх рисунка от низа. } static Int includesvertex(int h, int i, int j) /* Включает треугольник hlj другие ввершины? */ { Int I; double zh, zl, zj, zl, XH, YH, XI, Yl, XJ, YJ, XL. YL; pvertex - VERTEX + POLY[h]; zh - pvertex->ze; XH - pvertex->xe / zh; YH - pvertex->ye / zh; pvertex - VERTEX + POLY[i]; zl - pvertex->ze; XI - pvertex->xe / zl; Yl - pvertex->ye / zl; V V
312 Приложение Б. ФУНКЦИЯ УДАЛЕНИЯ НЕВИДИМЫХ ЛИНИЙ pvertex - VERTEX + POLY[j]; zj - pvertex->ze; XJ - pvertex->xe / zj; YJ - pvertex->ye / zj; for(l-0;Knpoly;i++) { if (!vertexconvex[l].&& I!- h && I!- i && I !-j) { pvertex - VERTEX + POLY[l]; zl - pvertex->ze; XL- pvertex->xe / zl; YL- pvertex->ye / zl; If (sameslde(XH, YH. XL. YL, XI, Yl, XJ, YJ) && sameslde(XI, Yl, XL, YL, XJ, YJ, XH, YH) && sameside(XJ, YJ, XL. YL, XH, YH, XI, Yl)) return 1; } } return 0; static Int sameslde(double xj, double yj, double xl, double yl, double xh, double yh, double xi, double yi) /* Точки j и I находятся с одной стороны от линии hi? */ { double a, b, с; a—yh—yi; b-xl-xh; c-xh*yi-yh*xi; return (a*xj+b*yj+c)*(a*xl+b*yl+c)X).0; } struct Iseglist *lsegnode(void) f { struct Iseglist *p; p-(struct Iseglist*) farmalloc((long)sizeof(struct Iseglist)); if (p - - NULL) ermes( "Память: отрезок прямой РСГ); return p; > struct linsegface *lsegfacenode(void) { struct linsegface *p; p-(struct linsegface *) farmalloc((long)sizeof(struct linsegface)); if (p - - NULL) ermes( "Память: ребра"); return p; } static int allocsize(int divfactor, int eisize) { long III; III - (farcoreleft( )/dlvfactor)/elsize; return (III < 32767 7 (int)lll: 32767); }
Литература Ammeraal,L. (1986). Programming Principles in Computer Graphics, Chichester: John Wiley. [Имеется перевод: Аммерал Л. Прин- ципы программирования в машинной графике. М.: 1992 ] Ammeraal,L. (1987). Сfor Programmers, Chichester: John Wiley. Ammeraal,L. (l9S7).ComputerGraphicsfortheIBMPC, Chichester: John Wiley. [Имеется перевод: Аммерал Л. Принципы программирования в машинной графике. М.: 1992 ] Ammeraal, L. (1987). Programs and Data Structures in C, Chichester: John Wiley. Borland International (1987). Turbo С User's Guide, Turbo С Refer- enceManual, TurboC Version L5 Additions and Enhancements. Coxeter, H. S. M. (1961). Introduction to Geometry, New York: John Wiley. Earle, J. H. (1987). Engineering Design Graphics, Reading, Mass.: Addison-Wesley. Escher, M. C, et al. (1972). The World ofM. С Escher, New York: Harry N. Abrams. - Foley, J. D., and A. van Dam (1982). Fundamentals of Interactive Computer Graphics, Reading,Mass.: Addison-Wesley. [Имеется перевод: Фоли Дж., вэн Дэм А. Основы интерактивной машинной графики: В 2-х книгах. — М.: Мир, 1985. ] Hearn, D., and М. P. Baker (1986). Computer Graphics, Englewood Cliffs, NJ: Prentice-Hall. Kernighan, B. W., and D. M. Ritchie (1978). The С Programming Language, Englewood Cliffs, NJ: Prentice-Hall. [Имеется перевод: Керниган Б., Ритчи Д. Язык программирования Си. — М.: Финансы и статистика, 1985.] Knuth, D. Н. (1968). The Art of Computer Programming, Vol. I, Reading, Mass.: Addison-Wesley. Newman, W. M.,, and R. F. Sproull (1979). Principles of Interactive Computer Graphics. New York: McGraw-Hill. [Имеется перевод первого издания (1973): Ньюмен У., Спрулл Р. Основы интерактивной машинной графики. — М.: Мир, 1976. ] Norton, Р. (1985). Programmer9 s Guide to the IBM PC, Washington: Microsoft Press. Pal, I. (1974). Raumgeometrie in der Technischen Praxis, Budapest: Akademiai Kiado.
предметный указатель Адаптер 204 аппроксимация поверхности 40 ANSI, стандарт языка Си 106 Вектор переноса 61 верхняя граница 27,55 вершина вогнутая 46 выпуклая 29 весь экран 31,33 вид (проекция) 17 расчлененный 101 с разрезом 82 видовые координаты 197 винтовая поверхность 97,186 винтовая резьба, прямоугольная 97 вспомогательные линии 31 В-сплайн кривая 85,154 поверхность 168 выполняемая программа 8 выходной файл 34 Гексаэдр 123,126 гладкая кривая 85 графический адаптер 204 грани 25,45 граница (номеров точек) 22, 56 графический режим 204 Диапазон точек 56 длины единица 11 додекаэдр 38,128 драйвер 226 Задняя грань 29, 46, 246 замкнутая кривая 159 золотое сечение 131 Изображения, размер 13 икосаэдр 137 интегрированная система 112 Кабель 90,160 классический стиль языка Си 106 ключевые слова языка Си static 225 void 109,110 команды программы D3D Axes — оси 31 Aspect — отношение сторон 33 Clear — очистить 25 Сору — копия 55 Delete — удалить 22, 25 Entire — весь экран 33 Faces — грани 27 Hidden — невидимые 35,51 Insert — ввод 20 Jump — перескок 25 List — список 30 Mode —режим 31,33 Move — перенос 55 Output — вывод на плоттер 35, 37 Print — печать 34 Quit — выход 20 Read — чтение 16 Transform — преобразование 55 Viewpoint — точка зрения 17 Write — запись 44 ? — (номер точки) 23 контур 245 конус 76,118 координатные оси 21 координаты глаза 197 кривая В-сплайна. 85,154 кривая гладкая 154 куб 25,67 курсор 23,204 Линейный список 246 линия наблюдения 11 Масштабирование 62,250 матрица переноса 151 матрица поворота 146 матричный принтер 33 минус, знак 48 мировые координаты 197 многогранник правильный 123 модель памяти 210 Направление проецирования 11 невидимые линии 33, 238 номера точек 20, 23, 31, 45 Область вывода 198, 204 обхода направление 28, 246 объектный файл 34, 42 ограничивающая призма 199 октаэдр 123, 127 ортогональные координаты 8
ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ 315 оси 16,31 отверстие 46 отношение сторон 33, 207 отражение 65 очистка экрана 207 ошибка, сообщение 107 Памяти модель 211 параллельная проекция 13,17 Пентагон 128 перенос 61 перспективное изображение 17 преобразование 19& пиксел 33,241 координаты 205 пирамида 76, 118 плоскость 29 отражения 65 проецирования 12 плоттер 34, 37, 247 поверхность В-сплайна 168 гладкая 40,245 поворот 57,146 пороговый угол 40, 245 передача информации 254 правая координатная система 9 преобразование 54 проецирования 198 призма 74,114 ограничивающая 199 принтер матричный 33 проволочная модель 26 программа D3D 260 PLOTHP 255 программная дискета 8, 15 проекция центральная 12 проектный файл 149 проекция 17 прототип функции 107 Размер изображения 13 расстояние наблюдения 12 расчлененный вид 101 режим по умолчанию 32 режим текстовый 206 рифовый узел 95 Сектор 83 сечение золотое 131 современный стиль языка Си 107 сообщение об ошибке 107 сообщений, область 16 спираль 87 список линейный 243 сплошное тело 11, 26, 28, 35 стандартные компоненты 73 стержень гибкий (кабель) 90,160 стирание страницы 207 экрана 209 сфера 77,120,141 сферические координаты 9 Текстовый режим 206 тело вращения 82 тетраэдр 124 толстые линии 16, 31 точка зрения по умолчанию 17 точка наблюдения 11,15,197 точка (символ) 27, 30, 46 трехмерные объекты 25 Турбо Си 105,225 Узел 92 рифовый 95 угол пороговый 40, 245 Файл драйвера 226 черчения 248 GRPACK 211 GRPACK1 227 HLPFUN 298 PLOTHP 255 TRAFO 150 фиксированная точка 60 функции прототип 107 функции initgr 234 initgraph 234 Центральная точка объекта 11, 197 проекция 12 цилиндр 74, 114, 179 Чертеж инженерный 17 Шрифт 236 Экран 156 33 экранные координаты 198 Эшер 97
Оглавление ПРЕДИСЛОВИЕ 5 Часть I. ИНФОРМАЦИЯ ДЛЯ ПОЛЬЗОВАТЕЛЯ Глава 1. ПРОЕКЦИИ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ 8 1.1. Координаты в трехмерном пространстве 8 1.2. Объект, точка наблюдения и перспективное изображение 11 1.3. Работа с точками и управление курсором 20 1.4. Отрезки прямых линий, грани и объекты 25 1.5. Весь экран, невидимые линии, печать 33 1.6. Объектные файлы 46 1.7. Вогнутые вершины, отверстия 46 1.8. Преобразования 54 1.8.1. Поворот 57 1.8.2. Перенос 60 1.8.3. Масштабирование 62 1.8.4. Зеркальное отражение 65 Глава 2. ПРИКЛАДНЫЕ ПРОГРАММЫ И УТИЛИТЫ 67 2.1. Объекты, сформированные из прямых призм 67 2.2. Некоторые другие стандартные компоненты 73 2.3. Тела вращения и разрезы 82 2.4. Гладкие пространственные кривые 85 2.5. Кабели и узлы 90 2.6. Винт с прямоугольной резьбой 97 2.7. Расчлененные виды 101
Часть II. ПРИКЛАДНЫЕ ПРОГРАММЫ Глава 3. ГЕНЕРАЦИЯ ВХОДНЫХ ФАЙЛОВ ДЛЯ ПРОГРАММЫ D3D 104 3.1. Прототипы функций на языке Си 104 3.2. Цилиндры и призмы 114 3.3. Конусы и пирамиды 118 3.4. Традиционная аппроксимация сферы 120 3.5. Правильные многогранники 123 3.5.1. Тетраэдр 124 3.5.2. Гексаэдр 126 3.5.3. Октаэдр 127 3.5.4. Додекаэдр 128 3.5.5. Икосаэдр 137 3.6. Аппроксимация сферы 80 треугольниками 141 3.7. Поворот в трехмерном пространстве 146 3.8. Кривые типа В-сплайна 154 3.9. Кабели 160 3.10. Поверхности типа В-сплайна 168 3.11. Пересечение цилиндров 179 3.12. Программа для прямоугольной винтовой резьбы 186 Часть III. ДЕТАЛИ ГРАФИЧЕСКИХ ПРОГРАММ Глава 4. ПРОГРАММЫ D3D И PLOTHP 195 4.1. Введение 195 4.2. Основной модуль программы D3D 197 4.3. Функции графики нижнего уровня 203 4.4. Удаление невидимых линий 238 4.5. Утилита для плоттеров фирмы HEWLETT-PACKARD 247 Приложение А. ПРОЕКТИРОВАНИЕ В ТРЕХМЕРНОМ ПРОСТРАНСТВЕ (Исходный текст модуля D3D) 260 Приложение Б. ФУНКЦИЯ УДАЛЕНИЯ НЕВИДИМЫХ ЛИНИЙ (Исходный текст модуля HLPFUN) 298 Литература 313 ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ 314
ВНИМАНИЕ РАЗРАБОТЧИКОВ! Московское предприятие БИНОМ предлагает подробные технические описания микросхем и документацию на кросс-системы программирования. Заказ документации - гарантийным письмом, по получении которого Вам будет выслан счет на оплату. Цена одного тома документации с учетом НДС по состоянию на 1 июля 1992 г. — 630 рублей. Точная цена указывается в счете, без оплаты которого документация не высылается. Убедительная просьба в гарантийном письме указать Ваш точный почтовый адрес, на чье имя высылать счет и документацию, а также телефон адресата. Гарантийные письма высылайте на адрес МП БИНОМ: 103473, Москва-473, а/я 133 БИНОМ. СПИСОК ТОМОВ ДОКУМЕНТАЦИИ Ниже приводится содержание томов технической документации, поставляемых МП БИНОМ. ТОМ 1. ОДНОКРИСТАЛЬНЫЕ МИКРОЭВМ СЕМЕЙСТВ МК48 И МК51 ТОМ 2. КРОСС-СИСТЕМЫ ПРОГРАММИРОВА- НИЯ ОМЭВМ СЕМЕЙСТВ МК48 И МК51 Для IBM-совместимых компьютеров. ТОМ 5. ПЕРИФЕРИЙНЫЕ БИС ПОДДЕРЖКИ МИКРОПРОЦЕССОРОВ И ОМЭВМ: КР1810ВК56, ВТ37, ВН59А, ВИ54. ТОМ 6. ИНТЕГРАЛЬНЫЕ МИКРОСХЕМЫ СЕРИИ КР1533. ТОМ 7. ИНТЕГРАЛЬНЫЕ МИКРОСХЕМЫ СЕРИИ КР1554.
вниманию системных программистов ремонтного персонала разработчиков аппаратуры Общество "КОНСУЛ" предлагает книгу "РУКОВОДСТВО ПО АРХИТЕКТУРЕ IBM PC AT". Авторами книги являются ведущие специалисты Минского НИИ ЭВМ. Вся приведенная информация есть результат обобщения многолетнего опыта коллектива авторов в исследованиях и разработке PC АТ-совместимых компьютеров. Полнота объема, последовательность изложения, подробность описания выгодно отличают книгу от других изданий и технических описаний, в которых бессистемно изложены сведения и случайно выбраны таблицы, рисунки, чертежи из "User's guide", чем грешит сегодня практически вся справочная литература по PC. При подготовке книги были использованы современные зарубежные издания по архитектуре, системному программированию и разработке аппаратных средств PC. В книге подробно изложена информация об архитектуре и подсистемах PC AT, приведены законченные примеры реализации программных и аппаратных средств на базе PC AT. Текстовый объем книги - 950 страниц энциклопедического формата. Заказ книги "РУКОВОДСТВО ПО АРХИТЕКТУРЕ IBM PC AT" — гарантийным письмом. По получении гарантийного письма Вам, в порядке очередности, будет выслан счет на оплату. Цена книги "РУКОВОДСТВО ПО АРХИТЕКТУРЕ IBM PC AT" с учетом НДС по состоянию на 1 июня 1992г.- 896 рублей. Точная цена указывается в счете, без оплаты которого книга не высылается.
Убедительная просьба в гарантийном письме указать Ваш точный почтовый адрес, на чье имя высылать счет и книгу, а также телефон адресата. Гарантийные письма высылайте на адрес 220012, г.Минск, ул.Толбухина, 21, Общество "Консул" содержание и объем книги "РУКОВОДСТВО ПО АРХИТЕКТУРЕ IBM PC AT" по разделам объем в стр. 1. Принципы построения процессора PC AT 150 2. Микропроцессоры и сопроцессоры 33 3. Архитектура и системное программирование микропроцессоров 80286/80386, 80287/80387 118 4. Системная память PC AT 40 5. Системная шина PC AT 30 6. Программируемые системные устройства 97 7. Система прерываний 25 8. Базовая система ввода-вывода (BIOS) 159 9. Клавиатура 25 10. Средства машинной графики 52 11. Интерфейсы PC AT 19 12. Дисковая подсистема 29 Приложения: 168 Функциональная схема процессора Общий список прерываний DOS-BIOS Прерывания и системные функции DOS Структура файлов типа ЕХЕ, загрузка и выполнение программ Пример программы для работы в защищенном режиме МП 80286/80386 Порты ввода-вывода процессорного ядра Порты программируемых системных устройств Порты адаптера MDA Порты адаптера CGA Порты адаптера EGA Порты адаптера VGA Система питания PC AT ( Полное оглавление занимает 5 страниц.)