Научная графика на языке Asymptote Обзорная монография Ю.М. Волченко 30.11.2014 - 4.01.2015 Îãëàâëåíèå Введение 4 I 8 ПРОГРАММИРОВАНИЕ 1 Базовые типы данных 9 2 Сложные типы данных 2.1 Массивы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Срезы массивов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3 Структуры . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 12 18 20 3 Операторы 3.1 Арифметические и логические операторы . 3.2 Префиксные и постфиксные операторы . . 3.3 Операторы, определяемые пользователем . 3.4 Условные операторы . . . . . . . . . . . . . 3.5 Циклы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 24 25 25 26 27 4 Функции 4.1 Аргументы по умолчанию . . . . . 4.2 Именованные аргументы . . . . . 4.3 Аргументы . . . (rest) . . . . . . . . 4.4 Математические функции . . . . . 4.5 Функции, определенные для пар . 4.6 Функции, определенные для троек 4.7 Строковые функции . . . . . . . . 4.8 Функции для путей path . . . . . . 4.9 Функции для путей guide . . . . . 4.10 Системные функции . . . . . . . . 4.11 Приведение типов . . . . . . . . . 4.12 Импорт . . . . . . . . . . . . . . . 4.13 Статические переменные . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 30 30 31 33 34 35 36 37 40 41 42 43 45 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Модули и их возможности 48 6 Основные инструменты 54 6.1 Установки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 6.1.1 Формат выходного файла . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 6.1.2 Размеры и процедура size . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 1 6.2 6.3 6.4 6.5 6.6 II Перья и опция pen . . . . . . . . . . . . 6.2.1 Цвет . . . . . . . . . . . . . . . 6.2.2 Тип линии . . . . . . . . . . . . 6.2.3 Толщина линии . . . . . . . . . . Преобразования и процедура transform Заполнение области и опция filltype . . Положение и направление . . . . . . . Фреймы и картинки . . . . . . . . . . . 6.6.1 Фреймы frame . . . . . . . . . . 6.6.2 Картинки picture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . РИСОВАНИЕ НА ПЛОСКОСТИ 54 55 57 58 58 59 60 61 61 61 64 7 Модуль plain 7.1 Процедуры dot и label . . . . . . . . . . . . 7.2 Кривые Безье . . . . . . . . . . . . . . . . 7.3 Процедуры path, guide и draw . . . . . . . . 7.4 Процедуры unitcircle, circle, ellipse и arc . . 7.5 Процедуры unitsquare, box и polygon . . . . 7.6 Заполнение, градиентная заливка, обрезка 7.7 Картинки picture . . . . . . . . . . . . . . . 7.8 Подпути и пересечения . . . . . . . . . . . 7.9 Фрагменты пути . . . . . . . . . . . . . . . 7.10 Создание замкнутых путей . . . . . . . . . 8 Модуль graph 8.1 Оси координат . . . . . . . . . . . . . . . . 8.1.1 Процедура xaxis . . . . . . . . . . . 8.1.2 Процедура yaxis . . . . . . . . . . . 8.1.3 Процедуры xlimits, ylimits и limits . 8.1.4 Процедуры xequals и yequals . . . . 8.1.5 Процедура axes . . . . . . . . . . . 8.1.6 Процедура axis . . . . . . . . . . . 8.2 Построение графиков . . . . . . . . . . . . 8.2.1 Сплайны . . . . . . . . . . . . . . . 8.2.2 Декартова система координат . . . 8.2.3 Параметрическое задание функции 8.2.4 Полярная система координат . . . . 8.2.5 Изображение векторных полей . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 65 67 68 74 75 76 79 80 82 83 . . . . . . . . . . . . . 87 87 87 90 91 92 93 94 95 95 96 105 106 107 9 Модуль palette 109 10 Модуль contour 113 11 Модули markers, labelpath и patterns 118 11.1 Модуль markers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 11.2 Модуль labelpath . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121 11.3 Модуль patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122 2 III РИСОВАНИЕ В ПРОСТРАНСТВЕ 12 Проекции, перспектива, освещение 12.1 Настройки . . . . . . . . . . . . . . . 12.1.1 Использование формата PRC 12.1.2 Разрешение изображения . . . 12.1.3 Размеры . . . . . . . . . . . . 12.2 Косоугольная проекция . . . . . . . . 12.3 Ортогональная проекция . . . . . . . 12.4 Перспектива . . . . . . . . . . . . . . 12.5 Освещение поверхности . . . . . . . . 125 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 . 126 . 126 . 126 . 127 . 127 . 128 . 130 . 131 13 Модуль three 13.1 Контуры и поверхности . . . . . . . . . . . . 13.2 Стрелки . . . . . . . . . . . . . . . . . . . . . 13.3 Окружности, дуги, прямоугольники и боксы 13.4 Трехмерные преобразования . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 . 149 . 153 . 156 . 156 . 159 . 161 . 161 . 161 . 163 . 165 . 166 . 168 . . . . . . . . . . . . . . . . . . . . . . . . 14 Модуль graph3 14.1 Оси координат . . . . . . . . . . . . . . . . . . 14.2 Координатные сетки и модуль grid3 . . . . . . 14.3 Графики функций как поверхности . . . . . . . 14.3.1 Декартова система координат . . . . . 14.3.2 Параметрическое задание поверхности 14.3.3 Цилиндрическая система координат . . 14.3.4 Сферическая система координат . . . . 14.3.5 Прозрачность и зачерчивание . . . . . 14.3.6 Проецирование, процедура lift . . . . . 14.3.7 Изображение векторных полей . . . . . 14.3.8 Раскрашивание с помощью палитр . . 14.3.9 Рельефные надписи на поверхности . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 133 138 139 142 15 Модули solids и contour3 170 16 Модули labelpath3 и tube 175 A План квартиры 180 B Зачерчивание I 183 C Зачерчивание II 187 D Чашка с напитком 191 Литература 193 3 Ââåäåíèå Если пользователь совершенно добровольно работает в такой «жуткой» среде как LATEX, значит, ему не все равно, как выглядит создаваемый им документ с эстетической точки зрения. С эстетикой в упомянутой издательской системе все в порядке. Но вот рисунки! Их надо создавать с помощью отдельных программ, из коих достойны употребления, по-видимому, только две (и обе бесплатные!): PGF/TikZ и Asymptote. Они, заботясь о едином стиле документа и рисунков в нем, обеспечили проникновение LATEX’а в свои операторы, команды и процедуры. В большей мере это свойственно PGF/TikZ и в меньшей – Asymptote. Но первая слабо справляется с трехмерной графикой (даже с учетом пакета PGFPLOTS), не имеет достаточно развитого языка программирования и довольно часто отказывается компилировать рисунки из-за нехватки памяти. Asymptote же представляет собой язык программирования столь высокого уровня, что создание с ее помощью самых сложных трехмерных рисунков превращается в изысканное удовольствие. Проще говоря, там, где в PGF/TikZ скрежет зубовный и «танцы с бубном», в Asymptote сочиняется само собой, как песня. Нехватка памяти случается и у нее, о чем в некоторых примерах этой книги пару раз упоминается. Но этот сбой встречается довольно редко и в действительно сложных ситуациях. Однако есть другие неприятные моменты. Надо хорошо постараться, чтобы в Asymptote вывести на рисунок русский текст, поэтому этим вопросом мы заниматься не будем. Что касается TEX’овских формул, Asymptote их понимает и выводит, но при корректировке размера шрифта в документе соответствующей корректировки на рисунках не произойдет. Кроме того, встраивание программы на Asymptote в TEX’овский текст неэффективно, так как требует трех проходов компиляции, скорость которой остается за пределами желаемого. Следовательно, рисунки лучше компилировать отдельно, а затем вставлять в текст. Если рисунок сохранить в pdf-формате, потери качества не произойдет. Что же в итоге? Для плоской графики следует предпочесть пакет PGF/TikZ, принимая во внимание его полную интеграцию с LATEX’ом, что в свою очередь обеспечивает вывод на рисунки русского текста. Собственно говоря, все примеры данного документа оформлены (не путать с созданием рисунков в Asymptote!) с помощью пакета tcolorbox, активно использующего пакет PGF/TikZ. А вот трехмерную графику лучше делать в Asymptote, поскольку лишь она достойно справится с этой задачей. Вот и ладушки? Ан нет! Последняя неприятность состоит в том, что достаточно полного руководства по Asymptote в природе не существует, что́ было не раз отмечено различными авторами. Руководство, которое можно скачать с официального сайта, довольно сухое, малоинформативное, совершенно не страдающее избытком примеров и даже использующее нигде в тексте не определенные ключевые слова. Возможно, создатели придерживались мнения, что пользователи хорошо знакомы с языками C, C++, Java и графической программой MetaPost, то есть со всем тем, что унаследовала Asymptote. Да и модули-исходники ее все открыты, 4 примеры в виде текстов программ имеются, читай и разбирайся! Короче, Asymptote – не для простых смертных! Конечно, грешно предъявлять претензии к авторам бесплатной программы – могли бы и программу не писать, не то, что руководство. Но простым смертным тоже некуда деваться, если позарез требуется трехмерная графика! Так что приходится по кусочкам собирать то, что имеется на английском, французском и русском и стряпать собственные опусы. Так возникла и эта книга: просто мне захотелось вставить в свои лекции, выложенные на двух сайтах, красивые трехмерные графики да еще с анимацией. Поэтому сей труд написан в основном для себя. Наверняка в нем есть опечатки, ошибки, ляпы, недопонимание и непонимание того, о чем здесь написано и т. д. Короче, все в лучших традициях бесплатных разработок! Итак, с чего же начать? Начнем с сайтов и литературы. Официальный сайт Asymptote такой: http://asymptote.sourceforge.net. Здесь можно скачать ее дистрибутив, руководство и получить адреса ссылок на страницы Интернет с примерами программ и изображений. В конце этой книги можно найти список литературы, которую я безжалостно проэксплутировал, сочиняя свою обзорную монографию. На то она и обзорная! Я не стал делать ссылки на источники по поводу каждого использованного мной примера, как и не стал специально отмечать собственные примеры. Не надо думать, что в книге дается достаточное полное изложение работы в Asymptote. За бортом остались многие вещи. Например, ничего не сказано о создании анимаций и интерактивных картинок. Не раскрыта работа с модулями, обеспечивающими статистические вычисления, рисование блок-схем, использование бинарных деревьев и т. д. Впрочем, один из них, модуль geometry, уже описан на русском языке [7]. Инсталлировать Asymptote лучше на диск C, так как у автора был случай, когда инсталлированная на диск D Asymptote отказалась делать надписи в рисунках не только на русском, но и на английском языке. В принципе программы для Asymptote можно писать в любом текстовом редакторе, но наиболее удобно это делать в блокноте notepad++. Он обеспечивает расцветку синтаксиса в стиле Asymptote, и в нем возможно проводить компиляцию программ и получать сообщения об ошибках. Кроме того, этот блокнот имеет свойство накапливать информацию об используемых пользователем словах и в дальнейшем подсовывать их в виде подсказок. Остается воспользоваться автозавершением и скорость набора команд существенно увеличится. Таким образом, вы получаете полноценную среду разработки программ. Особенно это подойдет тем пользователям, которые не собираются размещать рисунки в документах TEX’а, а планируют их вставлять, например, в документ Word’а или хранить в отдельном файле для демонстрации. Для полноценной работы Asymptote необходимо установить программу Ghostscript, систему MikTEX и просмотрщики картинок; например, для просмотра рисунков в формате pdf – программу SumatraPDF. Возможны и другие варианты. Для предотвращения возможных сбоев не следует создавать программу в блокноте, используя формат txt. Asymptote может отказаться работать с таким файлом. Лучше взять какой-нибудь файл примера из директории Asymptote с расширением asy, перенести его в свою рабочую директорию, удалить содержимое и набрать в нем свою программу. Теперь о настройке блокнота notepad++ для работы с Asymptote. Необходимо выполнить следующие действия. • Вызвать notepad++ и открыть в нем какой-нибудь asy-файл с каким-нибудь текстом (неважно, будет ли он правильным с точки зрения синтаксиса Asymptote). • Подключить плагин NppExec, для чего войти в главное меню блокнота и выполнить цепочку Плагины → Plugin Manager → Show Plugin Manager. Найти в появившемся списке NppExec и отметить ее галочкой. 5 • Щелкнуть мышкой по кнопке Install и на оба вопроса в двух диалоговых окнах ответить Да. • Войти в главное меню блокнота и выполнить Плагины → NppExec, после чего поставить галочки в позициях Show Console Dialog Console Commands History Save All Files on Execute Follow $(CURRENT_DIRECTORY) • Нажать F6 и после появления диалогового окна в поле Command(s) ввести код asy -f pdf -render 4 $(FILE_NAME) Нажать Save... и в поле Script name для сохранения pdf-файла ввести asy_pdf_render 4 Нажать Save и OK. В результате последнего действия произойдет компиляция программы. Если в ней нет ошибок, в pdf-просмотрщике появится откомпилированный рисунок; в противном случае в нижней части блокнота в разделе Console появится сообщение об ошибке. Дальнейшие компиляции выполняются при нажатии клавиш Ctrl+F6. Впрочем, есть один нюанс: иногда при правильно составленной программе, откомпилированный рисунок выводится на экран, но слово READY, означающее завершение компиляции, в разделе Console не появляется. Более того, попытка еще раз откомпилировать программу приводит к высвечиванию сообщения, что процесс еще не завершен, но его можно прервать насильно, но делать это не рекомендуется и пр. Происходит это не со всеми программами. Те, которые так себя ведут, лечатся просто, но некрасиво; в их текст надо вставить «холостой» оператор присваивания, например, такой: int zzzzz=1; Для красоты жизни можно добавить расцветку программ в соответствии с синтаксисом Asymptote. Для этого сначала надо в таблице сайта http://svn.gmaths.net найти файл расцветки UserDefineLang.xml и скачать его, щелкнув в таблице по Télécharger. Далее в блокноте зайти в главное меню и выполнить Синтаксисы → Задать свой синтаксис... В диалоговом окне щелкнуть Импортир... и вызвать скачанный файл. Затем закрыть окно и закрыть и снова открыть notepad++. Зайти в Синтаксисы и выбрать asy. Файл программы раскрасится яркими (даже чересчур!) красками. Постоянно работающим в системе LATEX можно рекомендовать TeXStudio – бесплатный редактор, в котором предусмотрена полная поддержка Asymptote, включая расцветку синтаксиса. Так что в этом редакторе можно и документы набирать, и рисунки делать. Для вызова Asymptote надо войти в главное меню TeXStudio и выполнить Инструменты → Команды → Asymptote. Единственное, что можно добавить для удобства, так это клавишный вызов Asymptote. С этой целью надо перейти в главное меню и активировать следующую цепочку: Параметры → Конфигурация TeXstudio → Горячие клавиши. Затем раскрыть узлы дерева справа: 6 Меню → Инструменты → Команды → Asymptote. Дважды щелкнуть мышью в графе Текущая и создать подходящий клавишный набор, например, Ctrl+Left. Естественно, все рассказанное проделывалось автором только в ОС Windows. И последнее. Если вас здорово перепугала первая часть книги, пропустите ее. Я так и сделал, когда самообучался Asymptote. Начните со второй части. Первую часть можно освоить потом, если понадобится создать какой-нибудь сложный рисунок. А в остальных случаях достаточно пользоваться ею как справочником по терминам, которые встречаются в остальных частях книги. 7 ×àñòü I Ïðîãðàììèðîâàíèå 8 Ãëàâà 1 Áàçîâûå òèïû äàííûõ Язык программирования в Asymptote подобен языкам программирования C, C++ и Java. Например, короткие комментарии обозначаются символами //, а более пространные – символами /* и */: // Ýòî - êîììåíòàðèé. x = x + 1; // Óâåëè÷èâàåì çíà÷åíèå ïåðåìåííîé íà 1. /* Çäåñü ïðèâîäèòñÿ îïèñàíèå ðèñóíêà ïîâåðõíîñòè, âûïîëíåííîãî â ñèñòåìå Asymptote */ Asymptote поддерживает следующие типы данных. void Тип функции, не имеющей аргументов. bool Обычный булевский тип со значениями true и false. Описание bool b = true; задает булевскую переменную b и инициализирует ее значением true. Если инициализация отсутствует, например, bool b;, то значением по умолчанию предполагается false. bool3 Расширенный булевский тип, принимающий значения true, default и false. По умолчанию инициализируется значением default. int Целое число, которое хранится в 64-битовом формате и принимает значения от −9 223 372 036 854 775 808 до 9 223 372 036 854 775 805 (т. е. от −263 до 263 − 3). Эти граничные значения хранятся в переменных intMin и intMax. real Число с плавающей точкой. По умолчанию инициализируется числом 0.0. Действительные числа имеют точность realEpsilon с realDigits значащими цифрами. Наименьшее положительное действительное число хранится в realMin, а наименьшее – в realMax. Могут быть полезны переменная inf и булевская функция isnan(real x), когда необходима маскировка исключений действительной арифметики с помощью опции командной строки -mask (по умолчанию в интерактивном режиме). pair Комплексное число, представляемое упорядоченной парой действительных компонент (x,y). Действительная и мнимая части пары z записываются как z.x и z.y. Их называют виртуальными членами пары; они не могут быть модифицированы напрямую. По умолчанию пара инициализируется значением (0.0,0.0). Комплексно сопряженное число можно получить несколькими способами: 9 Глава 1. Базовые типы данных 10 pair z = (3,4); z = (z.x,-z.y); z = z.x - I*z.y; z = conj(z); Здесь I – пара (0,1), conj – функция комплексного сопряжения. triple Упорядоченная тройка действительных чисел (x,y,z), используемая для изображений в трехмерном пространстве. Компоненты тройки v записываются как v.x, v.y и v.z. По умолчанию инициализируется значением (0.0,0.0,0.0). string Последовательность символов, соответствующих классу STL string. Строки заключаются в двойные кавычки ("), причем, должны удовлетворять следующим соответствиям, которые обеспечивают использование двойных кавычек в ТеХ’е: • \" соответствует " • \\ соответствует \\ Строки, заключенные в одинарные кавычки (’) должны отвечать тем же соответствиям, что и строки символов в ANSI C. • \' соответствует ’ • \" соответствует " • \? соответствует ? • \\ соответствует backslash • \a соответствует alert (ошибка) • \b соответствует пробел • \f соответствует прогон страницы • \n соответствует переход на новую строку • \r соответствует возврат каретки • \t соответствует табуляция • \v соответствует вертикальная табуляция • \0-\377 соответствует один из 8-ричных байтов • \x0-\xFF соответствует один из 16-ричных байтов По умолчанию строка считается пустой: "". Строки можно соединять с помощью оператора +. path Кубический сплайн, представляющий собой путь. Неявным представителем пути является nullpath. guide Нерешенный кубический сплайн, (список узлов и опорных точек кубического сплайна). Неявным инициализатором guide является nullguide. Тип guide похож на path, за исключением того, что вычисление кубического сплайна откладывается до времени рисования (когда он будет преобразован в путь). pen Перо. В Asymptote перьями задают стиль рисования. Перо, используемое по умолчанию, называется currentpen. Глава 1. Базовые типы данных 11 transform Преобразование. К этому типу данных относятся частные виды аффинных преобразований. picture Рисунок, картинка. Является основой рисования в пользовательских координатах и служит для манипуляций с частями изображения. По умолчанию рисование выполняется на картинке currentpicture. frame Полотно (холст). Холсты являются основой для рисования в PostScript-координатах. Ãëàâà 2 Ñëîæíûå òèïû äàííûõ 2.1 Ìàññèâû Добавление пары скобок [ ] к встроенному или пользовательскому типу делает последний массивом. Доступ к элементу i массива A может быть получен в виде A[i]. Задание отрицательного индекса массива считается ошибкой. Попытка получить элемент массива с индексом за границей индексов массива также вызывает ошибку. Тем не менее, введение нового элемента с таким индексом приводит к расширению массива с целью разместить в нем новый элемент. Возможна индексация массива A другим массивом B: массив A[B] формируется индексированием массива A элементами массива B. Декларация real[] A; создает пустой массив A нулевой длины. Пустые массивы следует отличать от null-массивов. Если мы набираем real[] A = null; то A вообще не может быть разыменован (null-массивы не имеют длины, из них нельзя читать и в них нельзя писать). Инициализируется массив следующим образом: real[] A={0,1,2}; Присвоение массива в Asymptote использует неглубокое копирование: копируется лишь указатель (если одна из копий модифицируется, то остальные также модифицируются). Функция copy, которая приводится ниже, применяет полное копирование. Каждый массив A типа T[ ] имеет следующие свойства. • int length • int cyclic • int[ ] keys • T push(T x) • void append(T[ ] a) • T pop() 12 Глава 2. Сложные типы данных 13 • void insert(int i ... T[ ] x) • void delete(int i, int j = i) • void delete() • bool initialized(int n) Свойство A.length означает длину массива. Установка A.cyclic = true указывает на то, что индексы массива должны быть приведены к текущей длине массива. Чтение из такого массива или запись в него не должны сопровождаться ошибками выхода за диапазон индекса или изменением длины массива. Свойство A.keys представляет собой массив целых, содержащий индексы инициализированных элементов массива A в возрастающем порядке. Таким образом, для массива длины n, у которого все элементы инициализированы, A.keys имеет вид {0, 1, . . . , n − 1}. Функции A.push и A.append добавляют свои аргументы в конец массива, в то время как A.insert(int i ... T[] x) вставляет x в массив в позицию i. Для удобства A.push возвращает вставленный элемент. Функция A.pop() выталкивает и возвращает последний элемент, в то время как A.delete(int i, int j = i) удаляет элементы с индексами из диапазона i ÷ j, уменьшая номера позиций всех элементов с бо́льшими номерами индексов. Если аргументы отсутствуют, A.delete() дает удобную возможность удаления всех элементов A. Процедура A.initialized(int n) может использоваться для проверки инициализации элемента с индексом n. Как и другие функции Asymptote, функции push, append, pop, insert, delete и initialized могут быть «отсоединены» от массивов и использоваться сами по себе. Рассмотрим примеры. • int[] A={1}; • A.push(2); // A = {1,2} • A.append(A); // A = {1,2,1,2} • f(3); // A = {1,2,1,2,3}. • write(g()); // Ïå÷àòàåò 3 • A.delete(0,1); // A = {2} • A.insert(1 ... A); // A = {2,2,3,3} • int f(int) = A.push; • int g() = A.pop; • A.delete(0); • A.insert(1,3); • A.insert(2,4,5); // A = {2,1,2} // A = {2,3} // A = {2,2,4,5,3,3} Суффикс [] может появляться и после имени переменной; иногда это удобно для объявления списка переменных и массивов одного типа. Например, real a,A[]; Глава 2. Сложные типы данных 14 объявляет a переменной типа real, а A – массивом типа real[]. В следующем списке встроенных функций T является обобщенным типом. Заметим, что встроенные функции alias, array, copy, concat, sequence, map и transpose, которые зависят от типа T[], определяются только после первого объявления переменной типа T[]. new T[] Создает новый пустой массив типа T[]. new T[] list Создает новый массив типа T[], заполненный элементами списка list (элементы последнего должны быть разделены запятыми). new T[n] Создает новый массив из n элементов типа T[]. Эти n элементов не инициализируются, если только сами не являются массивами (в этом случае они инициализируются как пустые массивы). T[] array(int n, T value, int depth = intMax) Возвращает массив, содержащий n копий value. Если value – массив, то для каждого элемента создается полная копия value. Если глубина копирования указана, то оно рекурсивно проводится для указанного числа уровней. int[] sequence(int n) Если n ≥ 1, возвращает массив {0, 1, . . . , n−1} (в противном случае возвращается null-массив). int[] sequence(int n, int m) Если m ≥ n возвращает массив {n, n + 1, . . . , m} (в противном случае возвращается null-массив). T[] sequence(T f(int), int n) Если n ≥ 1, формирует массив {fi : i = 0, 1, . . . , n − 1}, определяемый функцией T f(int) и целым n (в противном случае возвращается nullмассив). T[] map(T f(T), T[] a) Формирует массив применением функции f к каждому элементу массива a. Эквивалентно sequence(new T(int i) {return f(a[i]);},a.length). int[] reverse(int n) Если n ≥ 1, возвращает массив {n−1, n−2, . . . , 0} (в противном случае возвращается null-массив). int[] complement(int[] a, int n) Производит дополнение целочисленного массива a во множестве {0, 1, 2, . . . , n − 1}, так что b[complement(a,b.length)] производит дополнение b[a]. real[] uniform(real a, real b, int n) Если n ≥ 1, возвращает разбиение отрезка [a,b] на n одинаковых подынтервалов (в противном случае возвращается null-массив). int find(bool[], int n = 1) Возвращает индекс n-го значения true в массиве или −1, если ни одного такого значения нет. Если n отрицательно, поиск производится от конца массива до (−n)-го значения. int search(T[] a, T key) Для встроенных порядковых типов T отыскивает в отсортированном массиве a из n элементов такой индекс i, что a[i] ≤ key < a[i+1]. В случае невыполнения неравенства возвращает −1, если key меньше всех элементов a, и возвращает n-1, если key больше последнего элемента a или равен ему. int search(T[] a, T key, bool less(T i, T j)) В массиве a, отсортированном по возрастанию элементов, отыскивает такой элемент i, что i предшествует j, если less(i,j) принимает значение true. Глава 2. Сложные типы данных 15 T[] copy(T[] a) Делает полную копию массива a. T[] concat(... T[][] a) Формирует новый массив конкатенацией заданных одномерных массивов-аргументов. bool alias(T[] a, T[] b) Возвращает true, если массивы a и b идентичны. T[] sort(T[] a) Для встроенного порядкового типа T возвращает копию массива a, отсортированного в порядке возрастания элементов. T[][] sort(T[][] a) Для встроенного порядкового типа T возвращает копию массива a со строками, отсортированными по первому столбцу. Например: string[][] a={{"bob","9"},{"alice","5"},{"pete","7"},{"alice","4"}}; // Ñòðîêè ñîðòèðóþòñÿ ïî íóëåâîìó ñòîëáöó: write(sort(a)); дает alice 4 alice 5 bob 9 pete 7 T[] sort(T[] a, bool less(T i, T j)) Возвращает копию массива a, отсортированного в порядке возрастания таким образом, что элемент i предшествует элементу j, если less(i,j) принимает значение true. T[][] transpose(T[][] a) Транспонирует a. T[][][] transpose(T[][][] a, int[] perm) Возвращает транспозицию a, получаемую применением перестановки perm типа int[]{0,1,2} к индексам каждого элемента массива. T sum(T[] a) Для арифметического типа T возвращает сумму элементов массива a. Если T имеет булевский тип, подсчитывается число элементов true в массиве a. T min(T[] a) T min(T[][] a) T min(T[][][] a) Для встроенного порядкового типа T находит минимальный элемент массива a. T max(T[] a) T max(T[][] a) T max(T[][][] a) Для встроенного порядкового типа T находит максимальный элемент массива a. T[] min(T[] a, T[] b) Для встроенного порядкового типа T из массивов a и b одинаковой длины формирует массив, составленный из минимумов соответствующих элементов a и b. T[] max(T[] a, T[] b) Для встроенного порядкового типа T из массивов a и b одинаковой длины формирует массив, составленный из максимумов соответствующих элементов a и b. Глава 2. Сложные типы данных 16 pair[] pairs(real[] x, real[] y); Для массивов x и y одинаковой длины возвращает массив пар sequence(new pair(int i) {return (x[i],y[i]);},x.length). pair[] fft(pair[] a, int sign=1) Возвращает результат быстрого преобразования Фурье, выполненного над массивом a (если установлен опциональный пакет FFTW), используя заданный sign. Вот несколько простых примеров: int n=4; pair[] f=sequence(n); write(f); pair[] g=fft(f,-1); write(); write(g); f=fft(g,1); write(); write(f/n); real dot(real[] a, real[] b) Вычисляет скалярное произведение векторов a и b. pair dot(pair[] a, pair[] b) Возвращает скалярное произведение sum(a*conj(b)) комплексных чисел a и b. real[] tridiagonal(real[] a, real[] b, real[] c, real[] f) Решает периодическую трехдиагональную задачу Lx = f , получая решение x, где f – вектор размерности n, а L – матрица n × n: [ b[0] c[0] a[0] [ a[1] b[1] c[1] [ a[2] b[2] c[2] [ ... [ c[n-1] a[n-1] b[n-1] ] ] ] ] ] Для условий граничной задачи Дирихле (задаваемых u[-1] и u[n]) следует заменить f[0] на f[0]-a[0]u[-1] и f[n-1]-c[n-1]u[n], а затем положить a[0]=c[n-1]=0. real[] solve(real[][] a, real[] b, bool warn=true) Методом LU-декомпозиции находит решение x линейной системы ax = b, где a – матрица n × n, а b – вектор размерности n. Например: import math; real[][] a={{1,-2,3,0},{4,-5,6,2},{-7,-8,10,5},{1,50,1,-2}}; real[] b={7,19,33,3}; real[] x=solve(a,b); write(a); write(); write(b); write(); write(x); write(); write(a*x); Если a вырождена и warn имеет значение false, возвращается пустой массив. Если матрица a трехдиагональна, эффективнее использовать процедуру tridiagonal. Глава 2. Сложные типы данных 17 real[][] solve(real[][] a, real[][] b, bool warn=true) Решает систему ax = b, возвращая решение x, где a – матрица n×n, а b – матрица n×m. Если матрица a вырождена и warn имеет значение false, возвращается пустой массив. real[][] identity(int n); Возвращает единичную матрицу размера n × n. real[][] diagonal(... real[] a) Возвращает диагональную матрицу с диагональными элементами a. real[][] inverse(real[][] a) Вычисляет матрицу, обратную квадратной матрице a. real[] quadraticroots(real a, real b, real c); Находит действительные корни квадратного уравнения ax2 + bx + c = 0 и располагает их в порядке возрастания. Кратные корни заносятся в отдельный список. pair[] quadraticroots(explicit pair a, explicit pair b, explicit pair c); Находит комплексные корни квадратного уравнения ax2 + bx + c = 0. real[] cubicroots(real a, real b, real c, real d); Находит действительные корни кубического уравнения ax3 + bx2 + cx + d = 0. Кратные корни заносятся в отдельный список. В Asymptote имеется полный набор инструкций по выполнению арифметических и логических операторов над массивами векторного типа. Для ускорения вычислений эти инструкции реализованы на C++. Пусть заданы два массива real[] a={1,2}; real[] b={3,2}; тогда результатом сравнений a == b и a >= 2 в обоих случаях будет вектор {false, true}. Если требуется проверить выполнение сравнения для всех компонентов a и b, используют булевскую функцию all(a == b). Можно также применить условие вида (a >= 2) ? a : b, которое возвращает массив {3,2}, или write((a >= 2) ? a : null), возвращающее массив {2}. Все стандартные функции, встроенные в библиотеку libm† вида real(real) могут в качестве аргумента иметь и массив типа real. Это похоже на применение функции map. Как и другие встроенные функции, массивы могут быть прочитаны из файлов с помощью операции присваивания. В следующем примере программа file fin=input("test.txt"); real[] A=fin; читает переменные типа real в массив A, пока не будет достигнут конец файла (или не возникнет ошибка ввода-вывода). Такие свойства файлов как dimension, line, csv, word и read могут быть полезны и при чтении массивов. Например, если с помощью file line(bool b=true) установлен режим строки, то чтение будет прекращено при достижении ее конца: file fin=input("test.txt"); real[] A=fin.line(); Так как строка по умолчанию читается, пока не встретится ее конец, то режим строки не имеет никакого влияния на чтение массива строк. В то же время для чтения строк имеется режим пробелов file word(bool b=true), при котором чтение строки выполняется до появления пробела, а не конца строки: † Подключаемая в языке С библиотека стандартных математических функций. Глава 2. Сложные типы данных 18 file fin=input("test.txt").line().word(); real[] A=fin; Еще одним полезным режимом является режим запятой, file csv(bool b=true), при котором чтение строки происходит до встречи с разделителем-запятой: file fin=csv(input("test.txt")); real[] A=fin; Функцию file dimension(int) используют, чтобы ограничить количество прочитываемых значений : file fin=input("test.txt"); real[] A=dimension(fin,10); В массив A прочитывается 10 значений, если прежде не встретится конец файла Попытка прочитать за концом файла приводит к выводу сообщения об ошибке выполнения. Если количество прочитываемых значений ограничено нулем, это равносильно чтению до конца файла (или до конца строки в режиме строки). Дву- и трехмерные массивы базовых типов данных могут быть считаны подобно этому: file fin=input("test.txt"); real[][] A=fin.dimension(2,3); real[][][] B=fin.dimension(2,3,4); Опять-таки задание нулевого ограничения снимает ограничения на количество прочитываемых значений. Иногда размеры массива хранятся вместе с данными в целочисленных полях в начале массива. Поэтому одно-, дву- и трехмерные массивы могут быть прочитаны с помощью свойств read(1), read(2), read(3), соответственно: file fin=input("test.txt"); real[] A=fin.read(1); real[][] B=fin.read(2); real[][][] C=fin.read(3); Вывод одно-, дву- и трехмерных массивов базовых типов данных осуществляют функции write(file,T[]), write(file,T[][]), write(file,T[][][]), соответственно. 2.2 Ñðåçû ìàññèâîâ Asymptote позволяет использовать срезы массивов, синтаксис которых подобен синтаксису аналогичных структур в языке Python. Если A – массив, выражение A[m:n] возвращает новый массив, содержащий элементы A от элемента с номером m до элемента n−1. Например, int[] x={0,1,2,3,4,5,6,7,8,9}; int[] y=x[2:6]; // y={2,3,4,5}; int[] z=x[5:10]; // z={5,6,7,8,9}; Если левый индекс отсутствует, то он считается равным 0. Если отсутствует правый индекс, он берется равным длине массива. Если отсутствуют оба индекса, срез выполняется от начала до конца массива посредством глубокого копирования последнего. Примеры: Глава 2. Сложные типы данных int[] int[] int[] int[] 19 x={0,1,2,3,4,5,6,7,8,9}; y=x[:4]; // y={0,1,2,3} z=x[5:]; // z={5,6,7,8,9} w=x[:]; // w={0,1,2,3,4,5,6,7,8,9} - ìàññèâ, îòëè÷íûé îò ìàññèâà x. Для нециклического массива ни для одного из индексов нельзя задавать отрицательные значения. Если индексы превосходят длину массива, они усекаются до этой величины. Для циклических массивов срез A[m:n] по-прежнему означает элементы, индексированные числами из [m,n), но теперь допускаются как отрицательные индексы, так и индексы, превосходящие длину массива. Просто индексы идут по кругу: int[] x={0,1,2,3,4,5,6,7,8,9}; x.cyclic=true; int[] y=x[8:15]; // y={8,9,0,1,2,3,4}. int[] z=x[-5:5]; // z={5,6,7,8,9,0,1,2,3,4} int[] w=x[-3:17]; // w={7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6} Отметим, что для циклических массивов можно один и тот же элемент исходного массива включить в срез несколько раз. Независимо от цикличности или нецикличности исходного массива срез всегда нецикличен. Если левый и правый индексы среза одинаковы, в результате получится пустой массив. Если исходный массив пуст, то пустым будет и срез. Срез, у которого левый индекс превышает правый, вызовет ошибку. Для срезов можно выполнять присвоения, изменяя значения исходного массива. Если массив, присваиваемый срезу, имеет длину, меньшую, чем сам срез, для формирования нового массива некоторые элементы могут быть удалены или вставлены. Примеры: string[] toppings={"mayo", "salt", "ham", "lettuce"}; toppings[0:2]=new string[] {"mustard", "pepper"}; // Òåïåðü toppings={"mustard", "pepper", "ham", "lettuce"} toppings[2:3]=new string[] {"turkey", "bacon" }; // Òåïåðü toppings={"mustard", "pepper", "turkey", "bacon", "lettuce"} toppings[0:3]=new string[] {"tomato"}; // Òåïåðü toppings={"tomato", "bacon", "lettuce"} Если массив получают присвоением среза, взятого от этого массива, то копия исходного массива присваивается срезу. Т. о. запись x[m:n]=x равносильна записи x[m:n]=copy(x). Можно назначить x[m:m]=y, чтобы вставить содержимое массива y в массив x, начиная с позиции, непосредственно предшествующей x[m]. Для циклического массива срез является сочлененным, если он адресуется к элементам массива, номера которых идут до конца массива, а затем переходят на его начало. Например, если A – циклический массив длины 10, то A[8:12], A[-3:1] и A[5:25] являются сочлененными срезами, в то время как A[3:7], A[7:10], A[-3:0] и A[103:107] таковыми не являются. Для сочлененных срезов можно выполнять присвоения только тогда, когда число их элементов в точности равно числу присваиваемых им элементов, в противном случае возникает неопределенность, какой из новых элементов должен быть A[0], в результате чего возникает ошибка. Несочлененным срезам могут присваиваться массивы любой длины. Для циклического массива A выражение вида A[A.length:A.length] равносильно выражению A[0:0], так что в результате присвоения элементы будут вставлены в начало массива. В конец массива элементы вставляются с помощью A.append(). Не разрешается присваивать срезу циклический массив, у которого повторяются все его элементы. Глава 2. Сложные типы данных 20 2.3 Ñòðóêòóðû Пользователи могут создавать свои собственные типы данных вроде структур, так же как и собственные операторы, подобно тому, как это делается в C++. По умолчанию структуры имеют статус public (могут быть считаны и изменены в любом месте программы), но могут быть опционально объявлены как restricted (считываются, но писать в них можно лишь внутри структуры, где они определены) или private (считывают из них и пишут в них только внутри структуры). При определении структуры ключевое слово this может быть использовано как указатель на созданный экземпляр структуры. При инициализации выполняются все коды верхнего уровня структуры. Переменные содержат указатели на структуры. Рассмотрим пример: struct T { int x; } T foo=new T; T bar=foo; bar.x=5 Переменная foo содержит ссылку на экземпляр структуры T. Когда значение foo присваивается переменной bar, последняя тоже получает указатель на тот же экземпляр, что и foo. Присваивание bar.x=5 изменяет значение поля x и в экземпляре foo, так что foo.x тоже будет равно 5. Выражение new T создает новый экземпляр структуры T и возвращает указатель на этот экземпляр. При создании нового экземпляра выполнится код в теле структуры. Пример: int Tcount=0; struct T { int x; ++Tcount; } T foo=new T; Выражение new T не только сгенерирует новый экземпляр класса, но и увеличит Tcount. Выражение null может быть приведено к любому структурному типу, чтобы получить указатель, не указывающий ни на один экземпляр структуры. Попытка использовать поле с таким указателем ведет к ошибке. Функция bool alias(T,T) проверяет, не указывают ли два указателя структуры на один и тот же ее экземпляр (или оба – на null). В примере кода в начале раздела при создании нового экземпляра с помощью new T функция alias(foo,bar) дала бы значение true, а alias(foo,new T) вернула бы false. Булевские операторы == и != по определению равносильны alias и !alias, соответственно, но могут быть перекрыты для некоторых типов (например, при глубоком сравнении). Если структура T определена, переменная типа T автоматически инициализируется для создания нового экземпляра (с помощью new T). Тем не менее в момент определения структуры переменные типа T по умолчанию инициализируются значением null. Подобное поведение позволяет избежать бесконечной рекурсии при создании новых экземпляров. Пример: struct tree { int value; Глава 2. Сложные типы данных } 21 tree left; tree right; Следующий пример демонстрирует использование структур. struct S { real a=1; real f(real a) {return a+this.a;} } S s; // Èíèöèàëèçèðóåò s ñ ïîìîùüþ new S write(s.f(2)); // Âûâîäèòñÿ 3 S operator + (S s1, S s2) { S result; result.a=s1.a+s2.a; return result; } write((s+s).f(0)); // Âûâîäèòñÿ 2 Часто удобно иметь функции, конструирующие новые экземпляры структуры. Пусть определена структура Person: struct Person { string firstname; string lastname; } Person joe=new Person; joe.firstname="Joe"; joe.lastname="Jones"; Создание новой персоны довольно громоздко: потребовалось три строки для создания нового экземпляра и инициализации его полей (впрочем, при создании реальной особы требуется намного больше усилий). Мы можем упростить дело, определив конструктор Person(string,string): struct Person { string firstname; string lastname; } static Person Person(string firstname, string lastname) { Person p=new Person; p.firstname=firstname; p.lastname=lastname; return p; } Person joe=Person.Person("Joe", "Jones"); Глава 2. Сложные типы данных 22 Стало проще, но пришлось использовать идентификатор с расширением Person.Person. Если сразу после определения структуры добавить строку from Person unravel Person; можно обойтись без расширения: Person joe=Person("Joe", "Jones") Теперь конструктор стало использовать еще легче, но все эти определения требуют дополнительной работы. Если у вас в ходу несколько конструкторов, вы заметите, что приходится много писать повторяющегося кода. К счастью, разработчики Asymptote нашли способ автоматизировать большую часть процесса. Если в теле структуры встречается определение функции вида void operator init(args), Asymptote неявно создает конструктор с аргументами args, использующий упомянутую функцию void operator init для инициализации нового экземпляра структуры. То есть при этом фактически определяется следующий конструктор (пусть структура называется Foo): } static Foo Foo(args) { Foo instance=new Foo; instance.operator init(args); return instance; Этот конструктор передается в область видимости за пределами определения структуры, так что может быть задействован в дальнейшем без использования расширения в виде имени структуры. Наша персона теперь может быть реализована так: struct Person { string firstname; string lastname; } void operator init(string firstname, string lastname) { this.firstname=firstname; this.lastname=lastname; } Person joe=Person("Joe", "Jones"); Использование operator init при таком неявном определении конструкторов не следует путать с его использованием при определении значений по умолчанию для переменных. Действительно, в первом случае возвращаемый тип operator init должен быть void, а во втором, он должен быть (не void) типом переменной. Функция cputime() возвращает структуру процессорного времени с кумулятивными временами CPU, разбитыми на поля parent.user, parent.system, child.user и child.system. Для удобства инкрементные поля change.user и change.system фиксируют изменения в соответствующих общих родительских и дочерних временах CPU, начиная с момента последнего обращения к cputime(). Функция void write(file file=stdout, string s="", cputime c, string format=cputimeformat, suffix suffix=none); Глава 2. Сложные типы данных 23 выводит инкрементное пользовательское время CPU, следующее за “u”; инкрементное системное время CPU, следующее за s”; общее пользовательское время CPU, следующее за “U”; и общее системное время CPU, следующее за “S”. Приведение типов, во многом подобное тому, что имеется в C++, обеспечивает элегантную реализацию наследования структур, включая виртуальные функции: struct real void void void } parent { x; operator init(int x) {this.x=x;} virtual(int) {write(0);} f() {virtual(1);} void write(parent p) {write(p.x);} struct child { parent parent; real y=3; void operator init(int x) {parent.operator init(x);} void virtual(int x) {write(x);} parent.virtual=virtual; void f()=parent.f; } parent operator cast(child child) {return child.parent;} parent p=parent(1); child c=child(2); write(c); // Âûâîäèòñÿ 2 p.f(); c.f(); // Âûâîäèòñÿ 0 // Âûâîäèòñÿ 1 write(c.parent.x); // Âûâîäèòñÿ 2 write(c.y); // Âûâîäèòñÿ 3 Ãëàâà 3 Îïåðàòîðû 3.1 Àðèôìåòè÷åñêèå è ëîãè÷åñêèå îïåðàòîðû В Asymptote используются стандартные бинарные арифметические операторы. Тем не менее, если одно целое число делится на другое, оба аргумента перед делением преобразуются в действительные числа и результатом будет действительное частное (как оно обычно и подразумевается). Функция int quotient(int x, int y) возвращает наибольшее целое, меньшее или равное x/y. Во всех остальных случаях оба операнда приводятся к одному типу, который будет и типом результата: + Сложение. - Вычитание. * Умножение. / Деление. % Деление по модулю; знак результата совпадает со знаком делителя; в частности, выполняется q*quotient(p,q)+p%q == p для любого целого p и ненулевого целого q. Возведение в степень; если показатель степени (второй аргумент) – целое, используется рекурсивное умножение; в противном случае используется экспонента и логарифм; ** является синонимом . Определены также обычные логические операторы: == Равно. != Не равно. < Меньше. <= Меньше или равно. > Больше. >= Больше или равно. && Логическое «и» (правая часть не проверяется, если левая равна false). & Логическое «и» (правая часть всегда проверяется). 24 Глава 3. Операторы 25 || Логическое «или» (правая часть не проверяется, если левая равна true). | Логическое «или» (правая часть всегда проверяется). Исключающее «или», или операция сложения по модулю 2, или операция несовпадения: x^y = (x̄ ∧ y) ∨ (x ∧ ȳ) = (x̄ ∨ ȳ) ∧ (x ∨ y); возвращает значение true, если только один из операндов имеет значение true. ! Логическое «не». Еще имеются составные операторы присваивания +=, -=, *=, /=, %=, <<=, >>=, &=, |=, ^= Например, *= означает a=a*b. Функция T interp(T a, T b, real t) дает (1-t)*a+t*b для неинтегральных† встроенных арифметических типов T. Если a и b – перья, они сначала переводятся в одно и то же цветовое пространство. В Asymptote также определены битовые функции int AND(int,int), int OR(int,int), int XOR(int,int), int NOT(int), int CLZ(int) (подсчет количества лидирующих нулей) и int CTZ(int) (подсчет хвостовых нулей). 3.2 Ïðåôèêñíûå è ïîñòôèêñíûå îïåðàòîðû Как и в C, каждый из арифметических операторов +, -, *, /, % и может использоваться в самоприсваивании. Определены также префиксные операторы ++ (увеличить на 1) и -- (уменьшить на 1). Например, код int i=1; i += 2; int j=++i; равносилен коду int i=1; i=i+2; int j=i=i+1; Тем не менее постфиксные операторы, подобные i++ и i-- не определены (в силу возникновения неопределенности при использовании оператора сочленения путей --). В тех редких случаях, когда i++ и i-- действительно необходимы, можно применить выражения (++i-1) и (--i+1), соответственно. 3.3 Îïåðàòîðû, îïðåäåëÿåìûå ïîëüçîâàòåëåì Следующие символы могут быть использованы с именем operator для определения и переопределения операторов на структурах и встроенных типах: † Данное, описываемое как одно целое, называется интегральным, а описываемое с помощью нескольких понятий (например, мантиссы и экспоненты) – неинтегральным. В языке C к интегральным числовым данным относят char, int, short, long, signed, unsigned и enum, а к неинтегральным – float, double и long double. Глава 3. Операторы 26 - + / % ^ ! < > == != <= >= & | ^^ .. :: -- --- ++ << >> $ $$ @ @@ Операторы во второй строке имеют приоритет перед булевскими операторами <, >, <= и >=. Операторы пути типа .. могут быть перекрыты для написания функции, создающей новый путь из данного пути: guide dots(... guide[] g)=operator ..; guide operator ..(... guide[] g) { guide G; if(g.length > 0) { write(g[0]); G=g[0]; } for(int i=1; i < g.length; ++i) { write(g[i]); write(); G=dots(G,g[i]); } return G; } guide g=(0,0){up}..{SW}(100,100){NE}..{curl 3}(50,50)..(10,10); write("g=",g); 3.4 Óñëîâíûå îïåðàòîðû Asymptote поддерживает C-подобный условный оператор: real y = (x < 0) ? x+1 : 2.5; Применяется также оператор if как в упрощенном виде: if (<óñëîâèå> <îïåðàòîð 1>; else <îïåðàòîð 2>; так и в более общем: if (<óñëîâèå> {<îïåðàòîð 1>; ... <îïåðàòîð m>;} else {<îïåðàòîð m+1>; ... <îïåðàòîð n>;} Пример: if(x == 1.0) { write("x equals 1.0"); } else { write("x is not equal to 1.0"); } Глава 3. Операторы 27 3.5 Öèêëû Цикл for можно записать так: for(int i=0; i < 10; ++i) { write(i); } Имеются также циклы while: for(while k<= 5) { write(k); ++k; } и do-while: do {write(i); ++i;} while (i < 0); Цикл while выполняется до тех пор, пока логическое условие истинно. Цикл do-while выполняется по крайней мере один раз, даже если логическое условие не выполняется. Asymptote поддерживает такие же, как в C/C++, операторы while, do, break, continue, а также цикл, принятый в языке Java и организованный по элементам массива: int[] array={1,1,2,3,5}; for(int k : array) { write(k); } Ãëàâà 4 Ôóíêöèè Функции Asymptote рассматриваются как переменные с сигнатурой† (нефункциональные переменные имеют null-сигнатуру). Допустимы переменные с одинаковыми именами, если они имеют различные сигнатуры. Аргументы передаются функции по значению. Чтобы передать аргумент по ссылке, надо включить его в структуру. Вот некоторые существенные черты функций Asymptote. 1. Переменные с сигнатурой и без нее различаются: int x, x(); x=5; x=new int() {return 17;}; x=x(); // Âûçûâàåòñÿ ôóíêöèÿ x() è ðåçóëüòàò åå ðàáîòû, 17, // ïðèñâàèâàåòñÿ ñêàëÿðó x. 2. Допустимы традиционные определения функции: int sqr(int x) { return x*x; } sqr=null; // Ôóíêöèÿ åùå ÿâëÿåòñÿ è ïåðåìåííîé. 3. Для разрешения неопределенности используется механизм приведения типов: int a, a(), b, b(); // Ïðàâèëüíî: îïðåäåëÿþòñÿ 4 ïåðåìåííûå. a=b; // Íåïðàâèëüíî: ïðèñâàèâàíèå äâóñìûñëåííî. a=(int) b; // Ïðàâèëüíî: íåîïðåäåëåííîñòü óñòðàíÿåòñÿ. (int) (a=b); // Ïðàâèëüíî: íåîïðåäåëåííîñòü óñòðàíÿåòñÿ. (int) a=b; // Íåïðàâèëüíî: Âûðàæåíèÿ ïðè ïðèâåäåíèè òèïîâ // íå ìîãóò áûòü L-çíà÷åíèÿìè, òî åñòü çíà÷åíèÿìè, // êîòîðûå ìîãóò áûòü äîñòóïíû ïðîãðàììíî ïðè // âûïîëíåíèè ïðîãðàììû (â íåêîòîðîì ðîäå // óêàçàòåëè). int c(); c=a; // Ïðàâèëüíî: ëèøü îäèí âîçìîæíûé àðãóìåíò. † В C++ сигнатура простой функции – это ее имя и последовательность типов ее аргументов; если функция является методом некоторого класса, то в сигнатуре участвует и имя класса. 28 Глава 4. Функции 29 4. Разрешены также анонимные, или, так называемые, функции высшего порядка† : typedef int intop(int); intop adder(int m) { return new int(int n) {return m+n;}; } intop addby7=adder(7); write(addby7(1)); // Âûâîäèòñÿ 8. 5. Можно переопределить функцию f, даже в случае, если f вызывается в предварительно объявленных функциях, назначив для этого другую (анонимную или именованную) функцию. Тем не менее, если f перегружается с помощью нового определения функции, предварительно объявленные функции будут по-прежнему обращаться к исходной версии f, как показано в следующем примере: void f() { write("hi"); } void g() { f(); } g(); // Âûâîäèò "hi" f=new void() {write("bye");}; g(); // Âûâîäèò "bye" void f() {write("overloaded");}; f(); // Âûâîäèò "overloaded" g(); // Âûâîäèò "bye" 6. Анонимные функции можно использовать для переопределения функциональных переменных, которые уже объявлены (и инициализированы как null-функции), но еще до конца не определены: void f(bool b); void g(bool b) { if(b) f(b); else write(b); } f=new void(bool b) { write(b); g(false); † Принимает в качестве параметра другую функцию или возвращает функцию в качестве результата. Глава 4. Функции 30 }; g(true); // Âûâîäèòñÿ true, çàòåì - false. Видимо, Asymptote является единственным языком программирования, который обрабатывает функции как переменные, но допускает перегрузку переменных , отличающихся лишь сигнатурой. Asymptote разрешает рекурсивный вызов функции. Как и в С++, бесконечная рекурсия вызывает переполнение стека. 4.1 Àðãóìåíòû ïî óìîë÷àíèþ Асимптота поддерживает более гибкий механизм умолчания для аргументов функций, чем C++: аргументы могут появляться в любом месте прототипа функции† . Поскольку некоторые типы данных неявно приводятся к более сложным типам, часто можно избежать двусмысленности, упорядочивая аргументы функции от простейших к самым сложным. Например, функция real f(int a=1, real b=0) {return a+b;} для f(1) возвращает 1, а для f(1.0) возвращает 2.0. Значение аргумента по умолчанию определяется вычислением заданного выражения в том окружении, в котором определена вызываемая функция. 4.2 Èìåíîâàííûå àðãóìåíòû Не всегда бывает легко запомнить порядок, в котором аргументы появляются в объявлении функции. Именованные (ключевое слово) аргументы упрощают вызов функций с несколькими аргументами. В отличие от C и C++ языков, присваивание аргументу функции интерпретируется как присваивание параметра с таким же именем в сигнатуре функции, но не в области локальной видимости. Параметр командной строки -d может быть использован для проверки программы в случае, если именованный аргумент может стать причиной ошибки локального присваивания. При сопоставлении аргументов сигнатурам прежде всего сравниваются ключевые слова, а затем неименованные аргументы сравниваются с неименованными формальными параметрами, как обычно. Например, int f(int x, int y) { return 10x+y; } write(f(4,x=3)); выводит 34, так как x уже присвоено значение, когда происходит сопоставление неименованного аргумента 4, и, таким образом, это значение получает y. В тех редких случаях, когда желательно присвоить значение локальной переменной внутри аргумента функции (как правило, это не является хорошим стилем программирования), лучше заключить присвоение в скобки. Например, для данного в предыдущем примере определения функции f код † Прототипом функции в языке Си или C++ называется объявление функции, которое не содержит тела функции, но указывает ее имя, арность, типы аргументов и возвращаемый тип данных. Глава 4. Функции 31 int x; write(f(4,(x=3))); равносилен командам int x; x=3; write(f(4,3)); которые дают на выходе 43. Помещением keyword перед именем параметра последний помечается как «только с ключевым словом», например, int f(int keyword x) или int f(int keyword x=77). Это приводит к необходимости при вызове функции, чтобы задать значение этого параметра, использовать именованный аргумент. Таким образом, f(x=42) – правильно, а f(25) – нет. Параметры с обязательным ключевым словом при определении функции должны располагаться после обычных параметров. Отметим еще одну техническую деталь. Поскольку допустимы переменные с одинаковыми именами, но разными сигнатурами, то следующий код int f(int x, int x()) { return x+x(); } int seven() {return 7;} допустим для f(2,seven) и результатом будет 9. Именованный аргумент сопоставляется первому неименованному формальному параметру с таким же именем, поэтому f(x=2,x=seven) является равносильным вызовом функции, а f(x=seven,2) таковым не является, так как первый аргумент ставится в соответствие первому формальному параметру, но int () не может быть преобразовано в int. Параметры по умолчанию не работают при сопоставлении с именованными аргументами, так что для функции int f(int x=3, int x()) { return x+x(); } обращение f(x=seven) будет неправильным, хотя, очевидно, f(seven) сработает. 4.3 Àðãóìåíòû . . . (rest) Rest-аргументы (предваряются многоточием) позволяют использовать функции с неопределенным числом аргументов: // Ýòà ôóíêöèÿ ñóììèðóåò ñâîè àðãóìåíòû. int sum(... int[] nums) { int total=0; for(int i=0; i < nums.length; ++i) total += nums[i]; return total; } sum(1,2,3,4); // Âîçâðàùàåò 10 sum(); // Âîçâðàùàåò 0 Глава 4. Функции 32 // Ýòà ôóíêöèÿ èç ïåðâîãî àðãóìåíòà âû÷èòàåò âñå îñòàëüíûå. int subtract(int start ... int[] subs) { for(int i=0; i < subs.length; ++i) start -= subs[i]; return start; } subtract(10,1,2); // Âîçâðàùàåò 7 subtract(10); // Âîçâðàùàåò 10 subtract(); // Íåïðàâèëüíî Помещение аргументов в rest-массив называется упаковкой. Можно задать rest-аргумент как список, так что функция subtract может быть переписана и так: int subtract(int start ... int[] subs) { return start - sum(... subs); } Можно сочетать rest-аргументы с обычными аргументами: sum(1,2,3 ... new int[] {4,5,6}); // Âîçâðàùàåò 21 Здесь создается шестиэлементный массив, который воспринимается sum как nums. Обратная операция, распаковка: subtract(... new int[] {10, 1, 2}); недопустима, так как формальному параметру start ничего не сопоставлено. Если ни один аргумент не упакован, то с rest-параметром связывается массив нулевой длины (но не null). Заметим, что аргументы по умолчанию игнорируются для формальных параметров rest, а rest-аргумент не связывается с ключевым словом. Иногда бывают полезны параметры, используемые лишь с ключевыми словами. Это дает возможность избежать присвоения rest-аргументов другим параметрам. Например, в следующем примере применение keyword позволяет в pnorm (1.0,2.0,0.3) предотвратить сопоставление 1.0 с ð. real pnorm(real keyword p=2.0 ... real[] v) { return sum(v^p)^(1/p); } Метод перегрузки в Asymptote похож на правила сопоставления функций, используемые в C++. При сопоставлении аргументов предпочтение отдается точному соответствию. Затем в порядке предпочтения идут преобразование типов и упаковка в массив. Если соответствию удовлетворяют два или больше кандидатов-аргументов, возникает ошибка неоднозначности. int f(path g); int f(guide g); f((0,0)--(100,100)); // Îáðàùåíèå ê ôóíêöèè ñîîòâåòñòâóåò âòîðîìó åå // îáúÿâëåíèþ; àðãóìåíòîì áóäåò guide int g(int x, real y); int g(real x, int x); Глава 4. Функции 33 g(3,4); // Íåîäíîçíà÷íîñòü; ïåðâîå îáúÿâëåíèå ôóíêöèè // ïðåäïî÷òèòåëüíåå äëÿ ïåðâîãî àðãóìåíòà, âòîðîå // äëÿ âòîðîãî int h(... int[] rest); int h(real x ... int[] rest); h(1,2); // Âûáèðàåòñÿ ñîîòâåòñòâèå âòîðîìó îáúÿâëåíèþ ôóíêöèè, òàê êàê // õîòÿ îíî òðåáóåò ïðåîáðàçîâàíèÿ òèïà, íî ýòî ïðåäïî÷òèòåëüíåå, // ÷åì óïàêîâêà â ìàññèâ int i(int x ... int[] rest); int i(real x, real y ... int[] rest); i(3,4); // Íåîäíîçíà÷íîñòü; ïåðâîå îáúÿâëåíèå ôóíêöèè ïðåäïî÷òèòåëüíåå // äëÿ ïåðâîãî àðãóìåíòà, âòîðîå - äëÿ âòîðîãî 4.4 Ìàòåìàòè÷åñêèå ôóíêöèè Asymptote располагает встроеннными версиями действительных математических функций типа real (real) из библиотеки libm; имеются в виду sin, cos, tan, asin, acos, atan, exp, log, pow10, log10, sinh, cosh, tanh, asinh, acosh, atanh, sqrt, cbrt (кубический корень), fabs, expm1 (ex − 1), log1p (ln(1 + x)), как и тождественной функцией identity. Определены также функции Бесселя порядка n первого рода Jn(int n,real) и второго рода Yn(int n,real), гамма-функция gamma, функция ошибок erf, Z x 2 2 erf(x) = √ e−t dt, π 0 и дополнительная функция ошибок erfc, 2 erfc(x) = √ π Z x ∞ 2 e−t dt = 1 − erf(x). Включены в обиход также стандартные типа real (real,real) функции atan2 (обращение вида atan2(y,x) возвращает y/x в радианах), hypot (возвращает длину гипотенузы при заданных двух катетах), fmod, remainder (обращения с аргументами (x,y) к этим функциям возвращает остаток от деления x/y). Функции degrees(real radians) и radians(real degrees) переводят радианы в градусы и обратно. Функция Degrees(real radians) выдает угол в градусах из интервала [0;360). Для удобства в Asymptote имеются разновидности стандартных тригонометрических функций Sin, Cos, Tan, aSin, aCos, aTan, которые работают не с радианами, а с градусами. Имеются также комплексные версии функций sqrt, sin, cos, exp, log, gamma. Функции floor, ceil и round отличаются от своих обычных аналогов тем, что возвращают целое, а не действительное число (обычно именно это и требуется). Функции Floor, Ceil и Round, соответственно, действуют аналогично, за исключением того, что при невозможности преобразовать результат в подходящее целое они возвращают intMax для положительного аргумента и intMin – для отрицательного, не генерируя переполнения. Определена также функция sgn, которая возвращает знак своего действительного аргумента в виде одного из целых чисел −1, 0 или 1. Если Asymptote конфигурирована с библиотекой GNU Scientific Library† (GSL), доступной на сайте http://www .gnu .org /software /gsl /, то можно подключить модуль gsl, содержащий функции Эйри Ai(real), Bi(real), Ai_deriv(real), Bi_deriv(real), zero_Ai(int), † Библиотека численного анализа для C и C++. Глава 4. Функции 34 zero_Bi(int), zero_Ai_deriv(int), zero_Bi_deriv(int); функции Бесселя I(int, real), K(int, real), j(int, real), y(int, real), i_scaled(int, real), k_scaled(int, real), J(real, real), Y(real, real), I(real, real), K(real, real), zero_J(real, int); эллиптические функции F(real, real), E(real, real), P(real, real); эллиптические функции Якоби real[] sncndn(real,real); экспоненциальные/тригонометрические интегралы Ei, Si, Ci; полиномы Лежандра Pl(int, real); дзета-функцию Римана zeta(real). Например, для вычисления интегрального синуса от 1.0 следует набрать import gsl; write(Si(1.0)); Asymptote также предоставляет пользователю несколько численных процедур общего назначения. real newton(int iterations=100, real f(real), real fprime(real), real x, bool verbose=false); Для поиска корня действительной дифференцируемой функции f используется метод Ньютона-Рафсона, где fprime – производная заданной функции, x – начальная точка поиска. Если verbose=true, выводится диагностика каждой итерации. Если число итераций превзошло разрешенный максимум (iterations), возвращается realMax. real newton(int iterations=100, real f(real), real fprime(real), real x1, real x2, bool verbose=false); Для поиска корня действительной дифференцируемой функции f на отрезке [x1,x2] (на концах которого функция имеет значения противоположных знаков) применяется комбинированный метод Ньютона-Рафсона и бисекции, где fprime – производная заданной функции. Если verbose=true, выводится диагностика каждой итерации. Если число итераций превзошло разрешенный максимум (iterations), возвращается realMax. real simpson(real f(real), real a, real b, real acc=realEpsilon, real dxmax=b-a) Вычисляет интеграл от f в пределах от a до b методом Симпсона с адаптацией. 4.5 Ôóíêöèè, îïðåäåëåííûå äëÿ ïàð pair conj(pair z) Возвращает число, комплексно сопряженное числу z. real length(pair z) Вычисляет модуль комплексного числа z. Синонимом length(pair) является функция abs(pair). real angle(pair z, bool warn = true) Возвращает аргумент комплексного числа z в радианах в интервале [-pi,pi] или 0, если warn имеет значение false, а z = (0,0) (а не генерирует ошибку). real degrees(pair z, bool warn = true) Возвращает аргумент комплексного числа z в градусах в интервале [0,360) или 0, если warn имеет значение false, а z = (0,0) (а не генерирует ошибку). pair unit(pair z) Возвращает единичный вектор направления, определяемого парой z. Глава 4. Функции 35 pair expi(real angle) Возвращает единичный вектор направления, определяемого углом angle, заданным в радианах. pair dir(real degrees) Возвращает единичный вектор направления, определяемого углом angle, заданным в градусах. real xpart(pair z) Возвращает z.x. real ypart(pair z) Возвращает z.y. pair realmult(pair z, pair w) Выполняет покомпонентное умножение пар z и w, формируя пару (z.x*w.x,z.y*w.y). real dot(explicit pair z, explicit pair w) Вычисляет скалярное произведение пар z и w по правилу z.x*w.x+z.y*w.y. pair minbound(pair z, pair w) Возвращает (min(z.x,w.x),min(z.y,w.y)). pair maxbound(pair z, pair w) Возвращает (max(z.x,w.x),max(z.y,w.y)). 4.6 Ôóíêöèè, îïðåäåëåííûå äëÿ òðîåê real length(triple v) Находит модуль вектора v. Синонимом length(triple) является функция abs(triple). real polar(triple v, bool warn = true) Возвращает кошироту† вектора v в радианах или 0, если warn имеет значение false, а v = O (а не генерирует ошибку). real azimuth(triple v, bool warn = true) Возвращает долготу‡ вектора v в радианах или 0, если warn имеет значение false и v.x = v.y = 0 (а не генерирует ошибку). real colatitude(triple v, bool warn = true) Возвращает кошироту вектора v в градусах или 0, если warn имеет значение false, а v = O (а не генерирует ошибку). real latitude(triple v, bool warn = true) Возвращает широту†† вектора v в градусах или 0, если warn имеет значение false, а v = O (а не генерирует ошибку). real longitude(triple v, bool warn = true) Возвращает долготу вектора v в градусах или 0, если warn имеет значение false и v.x = v.y = 0 (а не генерирует ошибку). triple unit(triple v) Возвращает единичный вектор направления, определяемого тройкой v. triple expi(real polar, real azimuth) Находит единичный вектор направления, определяемого парой (polar,azimuth), заданной в радианах. triple dir(real colatitude, real longitude) Возвращает единичный вектор направления, определяемого парой (colatitude,longitude), заданной в градусах. real xpart(triple v) Возвращает v.x. real ypart(triple v) Возвращает v.y. † Угол между осью Oz и радиус-вектором точки в диапазоне от 0◦ до 180◦ . Угол между осью Ox и проекцией радиус-вектора точки на плоскость xOy в диапазоне от −180◦ до 180◦ . †† Угол между радиус-вектором точки и плоскостью xOy. ‡ Глава 4. Функции 36 real zpart(triple v) Возвращает v.z. real dot(triple u, triple v) Вычисляет скалярное произведение троек u и v по правилу u.x*v.x+u.y*v.y+u.z*v.z. triple cross(triple u, triple v) Вычисляет векторное произведение троек u и v по правилу (u.y*v.z-u.z*v.y,u.z*v.x-u.x*v.z,u.x*v.y-v.x*u.y). triple minbound(triple u, triple v) Возвращает (min(u.x,v.x),min(u.y,v.y),min(u.z,v.z)). triple maxbound(triple u, triple v) Возвращает (max(u.x,v.x),max(u.y,v.y),max(u.z,v.z)). 4.7 Ñòðîêîâûå ôóíêöèè int length(string s) Возвращает длину строки s. int find(string s, string t, int pos = 0) Возвращает позицию первого вхождения строки t в строку s после позиции pos (включительно), или −1, если t не является подстрокой строки s. int rfind(string s, string t, int pos = -1) Возвращает позицию последнего вхождения строки t в строку s до позиции pos (включительно, причем, если pos=-1, то это – конец строки s), или −1, если t не является подстрокой строки s. string insert(string s, int pos, string t) Вставляет строку t в строку s в позицию pos. string erase(string s, int pos, int n) Удаляет строку длины n (если n = −1, то до конца строки s) из строки s, начиная с позиции pos. string substr(string s, int pos, int n = -1) Возвращает подстроку строки s длины n (если n=-1, то до конца строки s), начиная с позиции pos. string reverse(string s) Обращает порядок символов в строке s на противоположный. string replace(string s, string before, string after) Заменяет в строке s все вхождения строки before на строку after. string replace(string s, string[][] table) По таблице-массиву table, состоящему из пар {before,after}, вхождения строк before заменяются соответствующими строками after. string[] split(string s, string delimiter = " ") Формирует массив строк, расщепляя строку s c помощью разделителя delimiter (пустой delimiter означает пробел, а сдвоенные разделители отбрасываются). string format(string s, int n, string locale = " ") Возвращает строку, содержащую n, отформатированное в соответствии со стилем С, заданным в строке s при использовании локаля† locale (или использовании текущего локаля, если заданная строка пуста). † Локаль – набор параметров, включая набор символов, язык пользователя, страну, часовой пояс, а также другие предустановки. Глава 4. Функции 37 string format(string s = defaultformat, string s = defaultseparator, real x, x string lokale = " ") Возвращает строку, содержащую x, отформатированной в соответствии со стилем С, заданным в строке s при использовании локаля locale (или использовании текущего локаля, если заданная строка пуста, повторяя поведение С-функции fprintf), за исключением случая, когда допускается лишь одно поле. Финальные нули по умолчанию не удаляются (если не указан #), а (если формат строки определен математическим режимом) для верстки математических выражений используется TEX, если defaultseparator = "\!\times\!";. int hex(string s) Преобразует 16-ричную строку s в целое число. int ascii(string s) Возвращает ASCII-код первого символа строки s. string string(real x, int digits = realDigits) Преобразует x в строку, используя точность digits и С-локаль. 4.8 Ôóíêöèè äëÿ ïóòåé path В простейшем случае path (путь) представляет собой полностью рассчитанный кубический сплайн. Его неявным инициализатором является nullpath. pair accel(path p, int t, int sign=0); Если sign < 0, возвращает входящее ускорение для узла t пути p; если sign > 0, возвращает выходящее ускорение. Если sign = 0, возвращает среднее значение этих ускорений. pair accel(path p, real t); Возвращает ускорение в точке t пути p. real arclength(path p); Возвращает длину (в пользовательских координатах) кусочно линейной или кубической кривой, представляющей путь p. real arcpoint(path p, real L); Возвращает point(p,arctime(p,L)). real arctime(path p, real L); Возвращает значение параметра пути, действительное число между 0 и длиной пути, в смысле point(path p, real t), для которого длина части пути от его начала равна L. path buildcycle(... path[] p); Возвращает путь, окружающий область, ограниченную не менее, чем двумя, последовательно пересекающимися путями. Пути вводятся списком p. slice cut(path p, path knife, int n); Возвращает в виде структуры struct slice { path before,after; } части пути p до и после его n-го пересечения с путем knife. В случае отсутствия пересечения весь путь считается before. Аргумент n трактуется как модуль числа пересечений. bool cyclic(path p); Возвращает true, если путь p цикличен. pair dir(path p, int t, int sign=0, bool normalize=true); Если sign < 0, возвращается направление (в виде пары) касательной, входящей в узел t пути p; если sign > 0, возвращается направление выходящей касательной. Если sign = 0, возвращается среднее значение этих двух направлений. Глава 4. Функции 38 pair dir(path p, real t, bool normalize=true); Дает направление касательной к пути p в точке между узлами floor(t) и floor(t)+1, отвечающей на кубическом сплайне параметру t-floor(t). pair dir(path p); Возвращает dir(p,length(p)). pair dir(path p, path q); Возвращает unit(dir(p)+dir(q)). real dirtime(path p, pair z); Возвращает первое значение параметра пути, действительное число между 0 и длиной пути, в смысле point(path p, real t), для которого касательная к пути имеет направление пары z. Если последнее не происходит, возвращает −1. pair extension(pair P, pair Q, pair p, pair q); Возвращает точку пересечения продолжений отрезков P--Q и p--q или (infinity,infinity), если отрезки не пересекаются. slice firstcut(path p, path knife); Равносильна cut(p,knife,0); bool interior(int windingnumber, pen fillrule); Выдает true, если windingnumber соответствует внутренней точке области действия fillrule. bool inside(path p, pair z, pen fillrule=currentpen); Возвращает true, если точка z лежит внутри области, ограниченной циклическим путем p и соответствующей действию fillrule, или на ее границе. int inside(path p, path q, pen fillrule=currentpen); Возвращает 1, если циклический путь p полностью охватывает путь q с учетом fillrule; возвращает −1, если циклический путь q полностью охватывает p; и возвращает 0 в противном случае. pair inside(path p, pen fillrule=currentpen); Возвращает точку, которую охватывает циклический путь p, с учетом fillrule. real[]intersect(path p, path q, real fuzz=-1); Если p и q имеют хотя бы одну точку пересечения, возвращает действительный массив длины 2, элемент которого [0] содержит значение параметра для пути p, а элемент [1] – значение параметра для пути q, при которых эти пути пересекаются (в смысле point(path, real)). Абсолютная погрешность вычислений определяется параметром fuzz, но, если fuzz < 0, – машинной точностью. Если пути не пересекаются, возвращает действительный массив длины 0. pair intersectionpoint(path p, path q, real fuzz=-1); Возвращает точку пересечения путей point(p,intersect(p,q,fuzz)[0]). pair[] intersectionpoints(path p, path q, real fuzz=-1); Возвращает массив, который содержит все точки пересечения путей p и q. real[][]intersections(path p, path q, real fuzz=-1); Возвращает значения параметров для всех (если только их не бесконечно много) точек пересечения путей p и q в виде отсортированного массива действительных массивов длины 2. Абсолютная погрешность вычислений определяется параметром fuzz, но, если fuzz < 0, – машинной точностью. real[]intersections(path p, explicit pair a, explicit pair b, real fuzz=-1); x Возвращает значения параметра для всех (если только их не бесконечно много) точек пересечения пути p с (бесконечной) прямой, проходящей через точки a и b, в виде отсортированного массива. Абсолютная погрешность вычислений определяется параметром fuzz, но, если fuzz < 0, – машинной точностью. Глава 4. Функции 39 slice lastcut(path p, path knife); Равносильна cut(p,knife,-1); int length(path p); Число сегментов (линейных или сплайновых) в пути p. Если путь p – циклический, то это то же самое, что и число узлов в p. pair max(path p); Возвращает пару (right, top), которая является правой верхней вершиной минимального прямоугольника, заключающего в себе путь p. real[] maxtimes(path p); Возвращает массив длины 2, содержащий значения параметра для пути p, при которых последний достигает максимальных горизонтальных и вертикальных уровней. pair midpoint(path p); Возвращает точку в середине пути p. pair min(path p); Возвращает пару (left, bottom), которая является левой нижней вершиной минимального прямоугольника, заключающего в себе путь p. real[] mintimes(path p); Возвращает массив длины 2, содержащий значения параметра для пути p, при которых последний достигает минимальных горизонтальных и вертикальных уровней. bool piecewisestraight(path p) Возвращает true, если путь p является кусочно постоянным. pair point(path p, int t); Если p представляет собой циклический путь, возвращаются координаты узла t mod length(p). В противном случае – координаты узла t, за исключением двух случаев: если t < 0, возвращается point(0), а при t > length(p) возвращается point(length(p). pair point(path p, real t); Возвращает координаты точки, расположенной между узлами floor(t) и floor(t)+1, отвечающей на кубическом сплайне параметру t-floor(t). Если t лежит вне диапазона [0,length(p)], то вначале производится редукция по модулю length(p), на чем для циклического пути вычисления заканчиваются; в противном случае еще выполняется преобразование к соответствующей концевой точке. pair precontrol(path p, int t); Возвращает предопорную точку узла t пути p. pair precontrol(path p, real t); Возвращает эффективную предопорную точку пути p для значения параметра t. pair postcontrol(path p, int t); Возвращает постопорную точку узла t пути p. pair postcontrol(path p, real t); Возвращает эффективную постопорную точку пути p для значения параметра t. real radius(path p, real t); Возвращает радиус кривизны в точке t пути p. pair relpoint(path p, real l); Возвращает точку на пути p, соответствующую относительной доле l длины пути arclength. real reltime(path p, real l); Возвращает значение параметра пути p, для которого относительная доля длины пути по сравнению с arclength равна l. path reverse(path p); Возвращает путь, который проходится в обратном направлении. int size(path p); Число узлов пути p. Если p цикличен, то это то же, что и length(p). Глава 4. Функции 40 bool straight(path p, int i); Возвращает true, если часть пути между узлами i и i+1 является отрезком прямой. path[] strokepath(path g, pen p=currentpen); Возвращает массив путей, который заполняет PostScript, вычерчивая путь g пером p. path subpath(path p, int a, int b); Возвращает подпуть пути p от узла a до узла b. Если a > b, то направление пути будет обратным. path subpath(path p, real a, real b); Возвращает подпуть пути p от значения параметра a до значения параметра b в смысле point(path, real). Если a > b, то направление пути будет обратным. real[] times(path p, real x); Возвращает все значения параметра для пути p, при которых последний пересекает вертикальную прямую, проходящую через точку (x,0). real[] times(path p, explicit pair z); Возвращает все значения параметра для пути p, при которых последний пересекает горизонтальную прямую, проходящую через точку (0,z.y). int windingnumber(path p, pair z); Возвращает порядок (число обходов, винтовое число) циклического пути p относительно точки z. Порядок пути положителен, если если путь обходит z против часовой стрелки. Если z принадлежит p, возвращается константа undefined (представляемая наибольшим нечетным целым). 4.9 Ôóíêöèè äëÿ ïóòåé guide Путь guide представляет собой нерешенный кубический сплайн (список узлов и опорных точек кубического сплайна). Неявным инициализатором для guide является nullpath; он полезен при построении guide с помощью цикла. pair[] controlSpecifier(guide g, int i); Если сегмент g между узлами i и i+1 имеет входящую и выходящую опорные точки, они возвращаются в качестве, соответственно, элементов 0 и 1 двухэлементного массива. В противном случае возвращается пустой массив. real[] curlSpecifier(guide g); Возвращает массив, содержащий начальный (как элемент 0) и конечный (как элемент 1) загибы (раздел 7.2) для g. bool cyclic(guide p); Аналог cyclic(path p). pair[] dirSpecifier(guide g, int i) Возвращает массив пар длины 2, содержащий выходящее (элемент 0) и входящее (элемент1) направления, определенные для сегмента g между узлами i и i+1. Возвращает (0,0), если ни одно из направлений не определено. int length(guide g); Аналог length(path p). pair point(guide g, int t); Аналог point(path p). guide reverse(guide g); Аналог reverse(path p). Если g цикличен и содержит еще один цикл, он сначала преобразуется в path, а затем выполняется обращение обхода. Если g не цикличен, но содержит внутренний цикл, только последний превращается в path, а затем выполняется обращение. Если внутренние циклы отсутствуют, превращение в path не происходит. Глава 4. Функции 41 int size(guide g); Аналог size(path p). tensionSpecifier tensionSpecifier(guide g, int i); Для сегмента g возвращает натяжение (раздел 7.2) между узлами i и i+1. Компоненты типа tensionSpecifier доступны в качестве виртуальных членов in, out и atLeast. 4.10 Ñèñòåìíûå ôóíêöèè string locale(string s = " ") Назначает локаль по данной строке, если она непуста, и возвращает текущий локаль. string time(string format = "%a %b %d %T %Z %Y") Возвращает текущее время, отформатированное процедурой strftime в ANSI в соответствии со строкой format и с использованием текущего локаля. Т. о. time(); time("\%a \%b \%d \%H:\%M:\%S \%Z \%Y"); равносильно получению текущего времени с помощью команды date в системе UNIX в формате, принятом по умолчанию. int seconds(string t = " ", string format = " ") Возвращает время в секундах после «эры Unix»‡ (Thu Jan 01 00:00:00 UTC 1970), определяемое в ANSI С-процедурой strptime в формате, задаваемом строкой format и текущим локалем, или текущее время, если строка t пуста. Заметим, что расширение "%Z"для POSIX-спецификации strptime игнорируется текущей библиотекой GNU C. При возникновении ошибки возвращается значение −1 . Вот несколько примеров: seconds("Mar 02 11:12:36 AM PST 2007","\%b \%d \%r PST \%Y"); seconds(time("\%b \%d \%r \%z \%Y"),"\%b \%d \%r \%z \%Y"); seconds(time("\%b \%d \%r \%Z \%Y"),"\%b \%d \%r "+time("\%Z")+" \%Y"); 1+(seconds()-seconds("Jan 1","\%b \%d"))/(24*60*60); В последнем примере возвращается текущее время, отсчитываемое от начала года. string time(int seconds, string format ="%a %b %d %T %Z %Y") Возвращает время в секундах от «Эры UNIX», определяемое в ANSI С-процедурой strptime в формате, задаваемом строкой format и текущим локалем. Например, чтобы получить момент времени, случившийся 24 часа назад, надо выполнить time(seconds()-24*60*60); int system(string s) x int system(string[] s) Если safe установлено в false, вызывается подходящая системная команда s. void asy(string format, bool overwrite = false ...string[] s) Обрабатывает имена файлов, помещенные в массив s, используя формат format, перекрывая выходной файл, только если overwriting имеет значение true. void exit() Осуществляет выход (с нулевым кодом возврата в пакетном режиме). void sleep(int seconds) Вызывает паузу в течение заданного числа секунд. ‡ «Эра UNIX» (англ. Unix Epoch) отсчитывается от полночи (по UTC) с 31 декабря 1969 г. на 1 января 1970 г. Глава 4. Функции 42 void usleep(int microseconds) Вызывает паузу в течение заданного числа микросекунд. void beep() Генерирует звуковой сигнал. 4.11 Ïðèâåäåíèå òèïîâ Asymptote неявно преобразует int в real, int в pair, real в pair, pair в path, pair в guide, path в guide, guide в path, real в pen, pair[] в guide[], pair[] в path[], path в path[] и guide в path[] наряду с различными трехмерными преобразованиями типов, определенных в модуле three.asy. Неявное приведение типов применяется автоматически при выполнения присваивания и при сопоставлении аргументов вызываемой функции с ее сигнатурой. Неявное приведение можно запретить, объявив explicit для некоторых переменных в сигнатуре функции. Например, чтобы избежать неоднозначности при вызове функции, возвращающей 0: int f(pair a) {return 0;} int f(explicit real x) {return 1;} write(f(0)); Другие преобразования, скажем, real в int или real в string, требуют явного приведения: int i=(int) 2.5; string s=(string) 2.5; real[] a={2.5,-3.5}; int[] b=(int []) a; write(stdout,b); // Âûâîäèòñÿ 2,-3 Для типов, определенных пользователем, преобразование типов выполняется с помощью operator cast: struct rpair { real radius; real angle; } pair operator cast(rpair x) { return (x.radius*cos(x.angle),x.radius*sin(x.angle)); } rpair x; x.radius=1; x.angle=pi/6; write(x); // Âûâîäèòñÿ (0.866025403784439,0.5) При определении новых операторов приведения, следует проявлять осторожность. Допустим, в программе необходимо все целые числа представлять кратными 100. Предположим, что вначале их преобразуют в действительные числа, а затем умножают на 100. Однако решение «в лоб» real operator cast(int x) {return x*100;} Глава 4. Функции 43 приводит к бесконечной рекурсии, так как результат x*100 вызывает сам себя для перевода из целого в действительное число. Вместо этого можно использовать стандартное приведение int к real: real convert(int x) {return x*100;} real operator cast(int x)=convert; Явное приведение выполняется аналогично с использованием operator ecast. 4.12 Èìïîðò Хотя Asymptote поддерживает множество возможностей по умолчанию, для некоторых приложений требуются специфические возможности, предоставляемые в Asymptote внешними модулями. Например, строки access graph; graph.axes(); рисуют оси x и y на плоскости. Первая команда ищет модуль graph в глобальном словаре модулей и помещает его в новую переменную под именем graph. Модуль является структурой, так что к его полям можно обращаться, как к полям обычной структуры. Часто удобнее использовать имена функций модуля, не обращаясь к его имени. Код from graph access axes; добавляет поле axes модуля graph в локальное пространство имен, после чего можно писать просто axes(). Если данное имя перекрыто, добавляются все типы и переменные этого имени. Для добавления более одного имени используется список с разделителями-запятыми: from graph access axes, xaxis, yaxis; Чтобы добавить в локальное пространство имен все типы и поля из разделов модуля, отличных от private, можно использовать wildcard-нотацию† : from graph access *; Аналогично можно добавить в локальную область типы структуры и ее поля из разделов, отличных от private, используя ключевое слово unravel: struct matrix { real a,b,c,d; } real det(matrix m) { unravel m; return a*d-b*c; } Вместо этого можно определить отдельные поля: } real det(matrix m) { from m unravel a,b,c as C,d; return a*d-b*C; † Wildcard-нотацией называется метод описания поискового запроса с использованием метасимволов (символовджокеров). Глава 4. Функции 44 Команда import graph; является удобной заменой команд access graph; unravel graph; т е. import graph сначала загружает модуль в структуру под названием graph, а затем добавляет его типы и поля из разделов, отличных от private, в локальную область. Таким образом, если имя переменной (или функции) из этой области используется какой-нибудь локальной переменной (или функцией такой же сигнатуры) изначальная переменная (или функция) остается доступной с помощью дополнения ее именем модуля. В большинстве случаев wildcard-нотация ведет себя нормально, но обычно пользователю не известны имена всех внутренних типов и переменных модуля, которые можно ненароком изменить. По этой причине благоразумнее ставить команды импорта в начале файла Asymptote, чтобы импортируемые имена не оказались перекрытыми именами локально определенных функций. Кроме того, импортируемые названия могут перекрыть другие импортируемые имена, в зависимости от порядка, в котором они импортировались, а импортированные функции могут привести к возникновению проблемы выбора требуемой функции, если названные функции имеют то же имя, что и локальные функции, определяемые далее. Для переименования или для полей, добавляемых в локальную область, применяют as: access graph as graph2d; from graph access xaxis as xline, yaxis as yline; Команда import graph as graph2d; является удобной заменой команд access graph as graph2d; unravel graph2d; За исключением нескольких встроенных модулей, таких, как settings, все модули реализованы в виде файлов Asymptote. При поиске еще не загруженного модуля, Асимптота обходит определенную последовательность директорий с целью сопоставления имени модуля имени файла. При состоявшемся сопоставлении, соответствующий код будет прочитан и будет трактоваться как тело структуры, определяющее модуль. Если имя файла содержит символы, не являющиеся буквами или цифрами, его следует взять в кавычки: access "/usr/local/share/asymptote/graph.asy" as graph; from "/usr/local/share/asymptote/graph.asy" access axes; import "/usr/local/share/asymptote/graph.asy" as graph; Если модуль импортирует сам себя, или модули в цикле импортируют друг друга, это считается ошибкой. В режиме реального времени можно импортировать модуль, определяемый строкой s: eval("import "+s,true); Для исполнения массива asy-файлов применяют Глава 4. Функции 45 void asy(string format, bool overwrite ... string[] s); Файл будет обработан с использованием формата format, если overwrite есть true или отсутствует выходной файл. Любое выражение Asymptote можно вычислить (без возврата результата), записав его в строку s и применив оператор void eval(string s, bool embedded=false); Нет необходимости заканчивать строку точкой с запятой. Если embedded есть true, строка будет вычислена на верхнем уровне текущего состояния. В противном случае (предполагаемом по умолчанию) строка будет вычислена в независимой среде с теми же самыми установками settings модуля. Можно вычислить произвольный код Asymptote (который может содержать немаскированные кавычки) с помощью команды void eval(code s, bool embedded=false); Вот код специального типа, в котором используется quote{}, чтобы вставить в Asymptote такой код: real a=1; code s=quote { write(a); }; eval(s,true); // Âûâîäèòñÿ 1 Чтобы вставить содержимое файла graph дословно (как если бы содержимое файла было просто перенесено в данное место), можно использовать один из вариантов вида include graph; include "/usr/local/share/asymptote/graph.asy"; Чтобы составить список всех глобальных функций и переменных, определенных в модуле, озаглавив список содержимым строки s, используется функция void list(string s, bool imports=false); Если переменная imports имеет значение true, то в список добавляются также импортируемые глобальные функции и переменные. 4.13 Ñòàòè÷åñêèå ïåðåìåííûå Для статической переменной адрес памяти выделяется в объемлющем блоке. В теле функции переменная размещается в том блоке, в котором определена функция; т. о. в коде struct s { int count() { static int c=0; ++c; return c; } } Глава 4. Функции 46 имеется один экземпляр переменной c для каждого объекта s (а не для каждого вызова count). Аналогично в следующем фрагменте int factorial(int n) { int helper(int k) { static int x=1; x *= k; return k == 1 ? x : helper(k-1); } return helper(n); } имеется один экземпляр x для каждого вызова factorial (но не для каждого вызова helper), так что это – хоть и правильная, но безобразная реализация вычисления факториала. Точно так же статическая переменная, объявленная внутри структуры, размещается в блоке, в котором определена структура. Т. о. struct A { struct B { static pair z; } } создает объект z для каждого созданного объекта типа A. В следующем примере int pow(int n, int k) { struct A { static int x=1; void helper() { x *= n; } } for(int i=0; i < k; ++i) { A a; a.helper(); } return A.x; } создается один экземпляр x для каждого вызова pow, так что теперь имеем и уродливую реализацию степенной функции. Циклы выполняют размещение на каждой итерации. Это сделано для того, чтобы высокого порядка функции могли обращаться к переменным, определенным для конкретных итераций цикла: void f(); for(int i=0; i < 10; ++i) { int x=i; if(x==5) { f=new void () { write(x); } } } f() Глава 4. Функции 47 Здесь каждая итерация цикла имеет собственную переменную x, так что f() напечатает 5. Если переменная в цикле объявлена статической, она будет размещена там же, где объявлена объемлющая функция или структура (как если бы статическая переменная была объявлена вне цикла). Например, во фрагменте: void f() { static int x; for(int i=0; i < 10; ++i) { static int y; } } и x, и y будут размещены в том же месте, в котором будет размещена f. Операторы также могут быть объявлены статическими, в этом случае они выполняются в том месте, в котором объявлена объемлющая функция или структура. Переменные или операторы, определенные вне функций или структур, объявлять статическими бессмысленно, так как они уже находятся на самом объемлющем уровне. В этом случае выдается предупреждение. Поскольку структуры могут иметь статические поля, то не всегда ясно, относится ли квалифицируемое имя к переменной или типу. Например, в коде struct A { static int x; } pair A; int y=A.x; возникает вопрос, к чему обращается A в A.x: к структуре или к переменной типа pair. В Asymptote действует соглашение о том, что, если нефункциональная переменная имеет то же имя, что и квалификатор, то последний обращается к переменной, а не к типу. Это не зависит от того, какие поля имеет переменная. Ãëàâà 5 Ìîäóëè è èõ âîçìîæíîñòè Asymptote содержит следующие модули. • Модуль animation служит для создания анимаций. В подкаталоге animations каталога examples имеются файлы примеров wheel.asy, wavelet.asy и cube.asy. Анимации используют программу ImageMagick convert для объединения серии изображений в gifили mpeg-клипы. Родственный пакет animate, производный от модуля animation, генерирует высокого качества компактные интерактивные pdf-клипы с дополнительными элементами управления. Требуется установка пакета http://www.ctan.org/tex-archive/macros/latex/contrib/animate/ animate.sty (версии 2007/11/30 или более поздней) в новый каталог animate локального каталога LATEX’а (например, в /usr/local/share/texmf/tex/latex/animate). В системах UNIX затем следует выполнить команду texhash. Возможности пакета иллюстрируют файлы примеров pdfmovie.asy и slidemovies.asy, intro.asy (слайд-презентации) из каталога animations. Примеры inlinemovie.tex и inlinemovie3.tex показывают, как создавать и вставлять pdf-клипы непосредственно в файл LATEX. Функция-член† string pdf(fit fit=NoBox, real delay=animationdelay, string options="", bool keep=settings.keep, bool multipage=true); структуры animate делает доступной любую из опций файла animate.sty, как это описано в http://www.ctan.org/tex-archive/macros/latex/contrib/animate/ doc/animate.pdf • Модуль annotate создает pdf-аннотации для просмотра с помощью Adobe Reader, используя функцию void annotate(picture pic=currentpicture, string title, string text, pair position) Аннотации приведены в файле annotation.asy. В настоящее время аннотации внедряются с помощью LATEX’а и TEX-движков. † Функция, определенная в какой-либо структуре. 48 Глава 5. Модули и их возможности 49 • Модуль babel вводит в Asymptote LATEX-пакет babel: import babel; babel("german"); • Модуль binarytree может быть использован для рисования различных бинарных деревьев и включает в себя процедуру для особого случая двоичного дерева поиска, как показано в примере binarytreetest.asy. • Модуль CAD (автор – Mark Henning) содержит основные определения перьев и функции измерений для простого CAD-черчения (не трехмерного) в соответствии с DIN 15. Документация к модулю находится в файле CAD.pdf. • Модуль contour обеспечивает построение контурных графиков (линий уровня функций двух переменных). • Модуль contour3 служит для построения поверхностей уровня функций трех переменных. • Модуль drawtree предназначен для рисования деревьев, см. пример treetest.asy. • Модуль embed предоставляет интерфейс для LATEX’а (в комплексе с MikTeX’ом) http://www.ctan.org/tex-archive/macros/latex/contrib/movie15 чтобы встраивать клипы, звук и 3D-объекты в pdf-документы. Пользователи XeLaTeX’а должны переименовать модифицированную версию movie15_dvipdfmx.sty http://asymptote.svn.sourceforge.net/viewvc/asymptote/trunk/ asymptote/patches/ в movie15.sty и поместить ее в путь LATEX’а. Последняя версия пакета movie15 требует версии 1.20 или более поздней программы pdflatex и файла http://www.ctan.org/tex-archive/macros/latex/contrib/oberdiek/ ifdraft.dtx который должен быть установлен в каталоге ifdraft локальной директории LATEX’а (например, в /usr/local/share/texmf/tex/latex/ifdraft), в которой должны быть затем выполнены команды tex ifdraft.dtx texhas • Модуль feynman (автор – Martin Wiebusch) обеспечивает рисование диаграмм Фейнмана, см. примеры eetomumu.asy и fermi.asy. • Модуль flowchart служит для вычерчивания блок-схем. • Модуль geometry, написанный Филиппом Ивальди, предоставляет обширный набор геометрических процедур. Документация к модулю: http://asymptote.sourceforge.net/links.html множество примеров: http://www.piprime.fr/files/asymptote/geometry/ оглавление: http://www.piprime.fr/files/asymptote/geometry/modules/geometry.asy. index.type.html Глава 5. Модули и их возможности 50 • Модуль graph обеспечивает построение линейных и логарифмических графиков на плоскости, включая автоматическое масштабирование и выбор отметок (с возможностью переопределения вручную). График является guide (то есть может быть нарисован процедурой draw и снабжен обозначениями). • Модуль graph3 содержит трехмерные версии функций модуля graph. Имеются специальные процедуры для построения координатных осей, поверхностей и векторных полей в пространстве. • Модуль grid3 (автор – Филипп Ивальди) обеспечивает черчение 3D-сеток. Примеры см. в файле grid3.asy и на странице http://www.piprime.fr/files/asymptote/grid3/ • Модуль interpolate позволяет использовать интерполяции Лагранжа, Эрмита и стандартную кубическую сплайн-интерполяцию, применяемую в Asymptote, что́ и показано в примере interpolate1.asy. • Модуль labelpath использует макрос pstextpath из пакета PSTricks, чтобы подогнать метки вдоль пути (правильно и с кернингом, как показано в примере curvedlabel.asy) c помощью процедуры void labelpath(picture pic=currentpicture, Label L, path g, string justify=Centered, pen p=currentpen); Опция justify принимает значения LeftJustified, Centered или RightJustified. Применительно к метке компонента x в преобразовании shift интерпретируется как сдвиг вдоль кривой, а компонента y – как сдвиг в сторону от кривой. Все другие преобразования метки игнорируются. Этот пакет требует LATEX’а или TEX-движка и наследует ограничения макро \pstextpath пакета PSTricks. • Модуль labelpath3 (автор – Jens Schwaiger) расширяет возможности labelpath на трехмерное пространство и не требует пакета PSTricks. См. пример curvedlabel3.asy. • Модуль latin1. Если в данной версии LATEX нет поддержки unicode, импортируя модуль latin1, можно включить поддержку западноевропейских языков (ISO 8859-1). Этот модуль может быть использован в качестве шаблона для поддержки других алфавитов ISO 8859. • Модуль markers реализует специализированные программы для маркировки путей и углов. Основной процедурой маркировки является markroutine markinterval(int n=1, frame f, bool rotated=false); которая центрирует n копий кадра f и размещает их равномерно вдоль пути с интервалом arclength при необходимости поворачивая на некоторый угол относительно локальной касательной. Примеры предопределенных маркеров можно посмотреть в markers1 и в markers2. Предусмотрены процедуры создания новых маркеров. • Модуль math расширяет математические возможности Asymptote с помощью некоторых полезных функций вроде следующих. void drawline(picture pic=currentpicture, pair P, pair Q, x pen p=currentpen); Не изменяя размеров рисунка pic и используя перо pen, рисует видимую часть (бесконечной) прямой, проходящей через точки P и Q. Глава 5. Модули и их возможности 51 real intersect(triple P, triple Q, triple n, triple Z); Возвращает параметр точки пересечения продолжения отрезка PQ (заданного в параметрической форме) с плоскостью, имеющей нормаль n и проходящей через точку Z. triple intersectionpoint(triple n0, triple P0, triple n1, triple P1); Возвращает произвольную точку пресечения двух плоскостей с нормалями n0 и n1 и проходящими через точки P0 и P1, соответственно. Если плоскости параллельны, возвращается (infinity,infinity,infinity). pair[] quarticroots(real a, real b, real c, real d, real e); Находит четыре комплексных корня уравнения четвертой степени ax4 + bx3 + cx2 + dx + e = 0. pair[][] fft(pair[][] a, int sign=1) Возвращает двумерное преобразование Фурье, используя заданное значение sign. real time(path g, real x, int n=0) Возвращает n-е значение параметра, при котором путь g пересекается вертикальной прямой, имеющей абсциссу x. real time(path g, explicit pair z, int n=0 Возвращает n-е значение параметра, при котором путь g пересекается горизонтальной прямой, имеющей ординату z.y. real value(path g, real x, int n=0) Вычисляет n-е значение ординаты g в точке x. real value(path g, explicit pair z, int n=0) Вычисляет n-е значение абсциссы g для ординаты z.y. real slope(path g, real x, int n=0) Возвращает n-й наклон g в точке x. real slope(path g, explicit pair z, int n=0 Возвращает n-й наклон g для ординаты z.y. int[][] segment(bool[] b) Возвращает индексы последовательностей из элементов true в массиве b. real[] partialsum(real[] a) Подсчитывает частичные суммы в действительном массиве a. real[] partialsum(real[] a, real[] dx) Подсчитывает частичные dx-взвешенные суммы в действительном массиве a. bool increasing(real[] a, bool strict=false) При strict=false возвращает true, если элементы a расположены в нестрого возрастающем порядке; если же strict=true возвращает true, если элементы a расположены в строго возрастающем порядке. int unique(real[] a, real x Если отсортированный массив a не содержит x, вставляет последний, возвращая индекс x в полученном массиве. bool lexorder(pair a, pair b) Возвращает true, если a.x < b.x || (a.x == b.x && a.y < b.y). bool lexorder(triple a, triple b) Возвращает true, если a.x < b.x || (a.x == b.x && (a.y < b.y || (a.y == b.y && a.z < b.z))). • Модуль MetaPost содержит несколько полезных процедур, облегчающих пользователям MetaPost’а преобразование старого MetaPost-кода в код Asymptote. Asymptote, в отличие от MetaPost’а, не решает неявно заданных линейных уравнений и поэтому не имеет команды whatever. Процедура extension является полезной заменой whatever: она находит точки пересечения линий, проходящих через p, q и P, Q. Менее употребительна замена whatever встроенным оператором solve. Глава 5. Модули и их возможности 52 • Модуль obj реализует построение поверхностей, опираясь на простые obj-файлы, см. примеры galleon.asy и triceratops.asy. • Модуль ode реализует ряд схем явного численного интегрирования для обыкновенных дифференциальных уравнений, см. пример odetest.asy. • Модуль palette позволяет генерировать плотность цвета изображения и палитры. Последние определены в файле palette.asy. • Модуль patterns реализует модели штриховки Postscript и включает несколько процедур генерация шаблонов штриховки. • Модуль plain по умолчанию является базовым модулем, определяющим основные составляющие языка рисования (например, структуру picture). Подключение этого модуля private import plain; происходит неявно до трансляции файла и перед первой командой, заданной в интерактивном режиме. Это относится также к трансляции файлов, определяющих модули. Сказанное означает, что типы и функции, определенные в plain, доступны почти во всех кодах Asymptote. • Модуль roundedpath (автор – Стефан Кнорр) скругляет острые углы путей, см. примеры в файле roundpath.asy. • Модуль simplex решает задачи линейного программирования двух переменных симплекс-методом. Используется в модуле plain для автоматической установки размеров рисунка. • Модуль slide предоставляет простое, но качественное средство для изготовления слайдов презентаций, в том числе переносных и со встроенной pdf-анимацией (см. пример slidemovies.asy). Простой пример приведен в файле slidedemo.asy. • Модуль slopefield предназначен для построения поля касательных к интегральным кривым дифференциального уравнения dy = f (x, y). dx • Модуль solids содержит структуру revolution, с помощью которой можно рисовать поверхности вращения. • Модуль stats реализует генерацию гауссовских случайных чисел и содержит набор статистических процедур, включая построение гистограмм и метод наименьших квадратов leastsquares. • Модуль syzygy автоматизирует процесс рисования кос, отношений и узлов, см. пример knots.asy. • Модуль tree реализует пример динамического двоичного дерева поиска. • Модуль three расширяет понятия path и guide на трехмерное пространство. Он вводит новые типы данных path3, guide3 и surface (поверхность). Синтаксис остается тем же, что и для случая двух измерений, только вместо пар (x,y) для узлов и спецификаторов направлений используются тройки (x,y,z). В модуле применяется обобщение сплайн-алгоритма Джона Хобби, которое shape-инвариантно при трехмерном вращении, масштабировании и сдвиге, и в плоском случае сводится к двумерному алгоритму, имеющемуся в Asymptote, MetaPost’е и MetaFont’е. Глава 5. Модули и их возможности 53 • Модуль trembling, также написанный Филиппом Ивальди, позволяет рисовать волнистыми линиями, имитирующими рисование от руки, см. пример floatingdisk.asy. Другие примеры размещены на странице http://www.piprime.fr/files/asymptote/trembling/ • Модуль tube предназначен для рисования трубчатых поверхностей, представленных в файле three_arrows.asy. • Модуль unicode. Команда import unicode в начале файла предписывает LATEX’у принять стандартизованные международные символы unicode (UTF-8). Для использования кириллических шрифтов необходимо изменить кодировку: import unicode; texpreamble("\usepackage{mathtext}\usepackage[russian]{babel}"); defaultpen(font("T2A","cmr","m","n")); При установке Ubuntu 12.04 этот способ не дает результата. Вместо него следует применить texpreamble("\usepackage[T2A]{fontenc}\usepackage[utf8]{inputenc} \usepackage{mathtext}\usepackage[russian]{babel}"); Два замечания. 1. Загрузка \usepackage{mathtext} и строка defaultpen(font("T2A "cmr "m "n"); не обязательны. 2. В математической моде (например, между знаками $$) русский текст не воспроизводится. Ãëàâà 6 Îñíîâíûå èíñòðóìåíòû 6.1 Óñòàíîâêè 6.1.1 Ôîðìàò âûõîäíîãî ôàéëà Устанавливается командой settings.outformat. Допустимы следующие форматы выходных файлов: eps (по умолчанию), png, pdf. Типичная установка формата выглядит так: settings.outformat = "pdf"; 6.1.2 Ðàçìåðû è ïðîöåäóðà size Компоненты пар выражаются в «больших пунктах» PostScript (1 bp = 1/72 дюйма). Например, линия по умолчанию имеет ширину 0.5 bp. Кроме того, размеры можно выражать в пунктах (1 pt = 1/72.27 inch), сантиметрах (cm), миллиметрах (mm) или дюймах (inch). Процедура size(real,real) имеет два аргумента-размера: максимальную ширину и максимальную высоту. Например, size(2cm,3cm); продуцирует рис., который будет иметь либо 2 см в ширину (и ≤ 3 см в высоту), либо 3 см в высоту (и ≤ 2 см в ширину). В любом случае отношение высоты к ширине сохраняется. Если какой-либо аргумент в size(real,real) равен нулю, он игнорируется. Таким образом, процедура size(4cm,0); масштабирует рис. таким образом, что его ширина будет точно равна 4 см. Высота же будет «естественной» высотой. Процедура size(real,real,keepAspect=false); масштабирует ширину и высоту независимо друг от друга, что приводит к тому, что рис. будет иметь заданные ширину и высоту, но отношение высоты к ширине может изменяться. Так, например, если окружность изображается на рис. с использованием этого типа процедуры size, то она рискует превратиться в эллипс. 6.2 Ïåðüÿ è îïöèÿ pen Эта опция используется для указания следующих атрибутов рисования: цвета (color), типа линии (line type), ее толщины (line width), формы ее концов (line cap), типа соединения (line join), правила заполнения (заливки) (fill rule), выравнивания текста (text alignment), вида шрифта (font), его размера (font size), штриховки (pattern), режима замены (overwrite mode), каллиграфического преобразования острия пера (calligraphic 54 Глава 6. Основные инструменты 55 transforms on the pen nib). По умолчанию используется перо currentpen. Неявным инициализатором является defaultpen. Различные перья могут объединяться в одно целое с помощью неассоциативного бинарного оператора +. Так получают сложение цветов. Например, можно получить желтое пунктирное перо, задав dashed+red+green, или red+green+dashed, или red+dashed+green. Бинарный оператор * можно использовать для получения оттенков цвета. Для этого надо умножить одну или нескольких цветовых компонент на действительное число, меньшее 1. 6.2.1 Öâåò По умолчанию рисование выполняется черным цветом, black; его можно изменить с помощью оператора defaultpen(pen). Цвета задаются в одном из следующих цветовых пространств. pen gray(real g); Дает оттенки серого цвета, интенсивность которого g лежит в промежутке [0; 1]; при этом 0.0 соответствует черному цвету, а 1.0 – белому. Предопределенный цвет gray имеет интенсивность 0.5. pen rgb(real r,real g,real b); Формирует RGB-цвет с интенсивностью составляющих цветов r (red, красного), g (green, зеленого), b (blue, синего) в пределах от 0 до 1. pen cmyk(real c,real m,real y,real k); создает CMYK-цвет с интенсивностью составляющих цветов c (cyan, голубого), m (magenta, пурпурного), y (yellow, желтого), b (black, черного) в пределах от 0 до 1. pen invisible; рисует невидимыми чернилами, создавая в то же время ограничивающий бокс, как и при обычном рисовании (аналогично команде \phantom в TEX’е). Может быть использована функция bool invisible(pen) для проверки того, является ли перо невидимым. Функция real[] colors(pen) возвращает цвета компонентов пера. Тройка других функций, pen gray(pen), pen rgb(pen) и pen cmyk(pen), создает новые перья, получаемые преобразованием аргументов этих функций в соответствующие цветовые пространства. Шестисимвольная шестнадцатеричная строка RGB может быть преобразована в перо процедурой pen rgb(string s); обратное преобразование пера в указанную строку выполняется процедурой string hex(pen p); Различные оттенки и смеси основных цветов серой шкалы (black и white, основных RGBцветов (red, green, blue), вторичных RGB-цветов (cyan, magenta, yellow), основных CMYKцветов (Cyan, Magenta, Yellow, Black) находятся в модуле plain (рис. 6.1). Команда import x11colors; импортирует стандартные 140 цветов RGB X11, а стандартные 68 CMYK-цветов TEX’а импортирует команда import texcolors; Отметим, что RGB- и CMYK-цвета частично совпадают, но некоторые значительно отличаются (например, green). В составе Asymptote имеется стилевой файл asycolors.sty в виде пакета LaTeX, в котором определены LaTeX’овские CMYK-версии предопределенных в Asymptote цветов. Так что они могут напрямую использоваться в строках документа LaTeX. Обычно для этого служат аргументы текущего пера, но иногда, чтобы изменить цвет только части строки, скажем, при создании презентации, бывает желательным определить цвет непосредственно в документе LaTeX. Этот файл может быть передан командой Asymptote usepackage("asycolors"); Структура hsv, определенная в файле plain_pens.asy, может быть использована для конвертации из HSV в RGB и обратно. Здесь цветовой оттенок h (hue) представляет собой угол из интервала (0; 360), а насыщенность s (saturation) и яркость v (value или brightness) принимают значения в диапазоне [0; 1]. Например, Глава 6. Основные инструменты 56 palered lightred mediumred red heavyred brown darkbrown palecyan lightcyan mediumcyan cyan heavycyan deepcyan darkcyan palegreen lightgreen mediumgreen green heavygreen deepgreen darkgreen pink lightmagenta mediummagenta magenta heavymagenta deepmagenta darkmagenta paleblue lightblue mediumblue blue heavyblue deepblue darkblue paleyellow lightyellow mediumyellow yellow lightolive olive darkolive black white orange fuchsia chartreuse springgreen purple royalblue Cyan Magenta Yellow Black cmyk(red) cmyk(blue) cmyk(green) palegray lightgray mediumgray gray heavygray deepgray darkgray Рис. 6.1. Наименования оттенков цветов. pen p=hsv(180,0.5,0.75); write(p); // ([default], red=0.375, green=0.75, blue=0.75) hsv q=p; write(q.h,q.s,q.v); // 180 0.5 0.75 В Asymptote есть предопределенные палитры цветов, используемые для закрашивания и хранящиеся в модуле palette.asy: pen[] Grayscale(int NColors = 256) // Ïàëèòðà ñåðîãî öâåòà pen[] Rainbow(int NColors=32766) // Ïàëèòðà-ðàäóãà pen[] BWRainbow(int NColors=32761) // Ïàëèòðà-ðàäóãà ñ äîáàâëåíèåì áåëîãî // è ÷åðíîãî öâåòîâ íà êîíöàõ ñïåêòðà pen[] BWRainbow2(int NColors=32761) // Äâîéíàÿ ïàëèòðà-ðàäóãà ñ äîáàâëåíèåì // áåëîãî è ÷åðíîãî öâåòîâ íà êîíöàõ ñïåêòðà è ëèíåéíî èçìåíÿþùåéñÿ // èíòåíñèâíîñòüþ öâåòà pen[] Wheel(int NColors=32766) // Ïîëíûé íàáîð öâåòîâ pen[] Gradient(int NColors=256 ... pen[] p) // Ïàëèòðà ëèíåéíî èçìåíÿþùèõñÿ // öâåòîâ, çàäàííûõ ìàññèâîì ïåðüåâ (pen) èç NColors Эти палитры показаны на рис. 6.2. Причем, на последней картинке показана палитра типа палитры violet из системы pgfplots, выполненная с помощью следующих команд: Глава 6. Основные инструменты 57 pen p1=rgb(.1,.1,.48); pen p2=rgb(.93,.55,.93); Gradient(p2,white,p1); Grayscale Rainbow BWRainbow BWRainbow2 Wheel Пользовательская Рис. 6.2. Предопределенные палитры. 6.2.2 Òèï ëèíèè Этот параметр задает процедура pen linetype(real[] a,real offset=0,bool scale=true,bool adjust = true) Аргумент a означает массив действительных чисел. Первое число массива указывает длину первого штриха, второе число – длину первого пробела и т. д. Если scale=true, то единицей длины считается единица длины пера, в противном случае – PostScript-единица. Если adjust=true, то эти расстояния регулируются Asymptote автоматически в зависимости от длины пути. Необязательный аргумент offset определяет смещение в шаблоне штриховки. Имеются предопределенные типы линий: pen pen pen pen solid=linetype(new real[]); dotted=linetype(new real[] {0,4}); dashed=linetype(new real[] {8,8}); longdashed=linetype(new real[] {24,8}); // // // // Ñïëîøíàÿ ëèíèÿ Ïóíêòèðíàÿ ëèíèÿ Øòðèõîâàÿ ëèíèÿ Äëèííàÿ øòðèõîâàÿ ëèíèÿ Глава 6. Основные инструменты 58 pen dashdotted=linetype(new real[] {8,8,0,8}); // Øòðèõïóíêòèðíàÿ ëèíèÿ // Äëèííàÿ øòðèõïóíêòèðíàÿ ëèíèÿ pen longdashdotted=linetype(new real[] {24,8,0,8}); pen Dotted(pen p=currentpen) {return linetype(new real[] {0,3})+2*linewidth(p);} pen Dotted=Dotted(); // Ïóíêòèðíàÿ ëèíèÿ Типом линии по умолчанию считается solid. Впрочем, его несложно изменить с помощью определения defaultpen(pen). Тип линии, производимой пером может быть задан функциями real[] linetype(pen p = currentpen), real offset(pen p), bool scale(pen p) и bool adjust(pen p) 6.2.3 Òîëùèíà ëèíèè Толщина линии определяется толщиной пера и указывается как pen linewidth(real) (в PostScript-единицах). По умолчанию толщина линии составляет 0.5 bp. Это значение изменяет процедура defaultpen(pen). Функция real linewidth(pen p=currentpen) возвращает толщину линии. Для удобства в модуль plain включены следующие определения. static void defaultpen(real w) defaultpen(linewidth(w)); static pen operator +(pen p,real w) return p+linewidth(w); static pen operator +(real w,pen p) return linewidth(w)+p; Так что допустимы такие определения толщины линии defaultpen(2); pen p=red+0.5; 6.3 Ïðåîáðàçîâàíèÿ è ïðîöåäóðà transform В Asymptote широко применяются аффинные преобразования. Пара (x,y) с помощью оператора t=(t.x,t.y,t.xx,t.xy,t.yx,t.yy) преобразуется в пару (x',y'), где x' = t.x + t.xx * x + t.xy * y y' = t.y + t.yx * x + t.yy * y Это равносильно PostScript-преобразованию [t.xx t.yx t.xy t.yy t.x t.y]. Преобразования применяются к парам (pair), путям (path и guide), строкам (string), холстам (frame), картинкам (picture) и другим преобразованиям (transform) с помощью левого умножения (бинарный оператор *). Преобразования могут составлять композиции и обращаться с помощью функции transform inverse(transform t); они также могут возводиться в любую целую степень оператором ^. Имеются следующие встроенные преобразования. transform identity(); Тождественное преобразование. transform shift(pair z); Сдвиг на вектор z. Глава 6. Основные инструменты 59 transform shift(real x, real y); Сдвиг на вектор (x,y). transform xscale(real x); Масштабирование в x раз в направлении оси Ox. transform yscale(real y); Масштабирование в y раз в направлении оси Oy. transform scale(real s); Масштабирование в s раз в направлении обеих координатных осей. transform scale(real x, real y); Масштабирование в x раз в направлении оси Ox и в y раз в направлении оси Oy. transform slant(real s) Наклон: точка (x,y) переводится в точку (x+s*y,y). transform rotate(real angle, pair z=(0,0)) Поворот вокруг точки z на угол angle (в градусах). transform reflect(pair a, pair b); Отражение относительно прямой a--b. Неявной инициализацией преобразований является identity(). Процедуры shift(transform t) è shiftless(transform t) возвращают преобразования (t.x,t.y,0,0,0,0) и (0,0,t.xx,t.xy,t.yx,t.yy), соответственно. 6.4 Çàïîëíåíèå îáëàñòè è îïöèÿ lltype Для заполнения областей цветом в Asymptote имеется опция filltype. Она может принимать следующие значения. FillDraw Заполняет внутреннюю часть области и рисует ее границу. FillDraw(real xmargin=0, real ymargin=xmargin, pen fillpen=nullpen, pen drawpen=nullpen) Если fillpen равно nullpen, заполнение выполняется пером drawpen; в противном случае – пером fillpen. Если drawpen=nullpen, то граница рисуется пером fillpen; в противном случае – пером drawpen. Можно задать ширину заполнения с помощью xmargin и ymargin. Fill Заполняет внутренность области. Fill(real xmargin=0, real ymargin=xmargin, pen p=nullpen) Если p=nullpen, заполнение выполняется пером drawpen; в противном случае – пером p. Можно задать ширину заполнения с помощью xmargin и ymargin. NoFill Заполнение не происходит. Draw Рисуется только граница. Draw(real xmargin=0, real ymargin=xmargin, pen p=nullpen) Если p=nullpen, граница рисуется пером drawpen; в противном случае – пером p. Можно задать ширину заполнения с помощью xmargin и ymargin. UnFill Обрезка области. UnFill(real xmargin=0, real ymargin=xmargin) Обрезает область, окружая ее границами xmargin и ymargin. Глава 6. Основные инструменты 60 RadialShade(pen penc, pen penr) Заполнение идет радиально от центра ограничивающего прямоугольника пером penc до границы области пером penr. RadialShadeDraw(real xmargin=0, real ymargin=xmargin, pen penc,pen penr, pen drawpen=nullpen) Сначала выполняется заполнение с помощью RadialShade, а затем рисуется граница. Перо с PostScript’овским правилом заполнения возвращается с одним из двух целых значений: pen zerowinding=fillrule(0); pen evenodd=fillrule(1); Правило заливки оказывает влияние только на функции clip, fill и inside и использует алгоритм, который применяется для определения внутренней части области, ограниченной путем или массивом путей. Для правила zerowinding (принятого по умолчанию) точка z лежит вне области, ограниченной путем, если число пересечений снизу вверх пути и горизонтальной линии z--z+infinity минус число нисходящих пересечений равно нулю. Для правила evenodd точка z считается расположенной вне указанной области, если общее число таких пересечений четно. Правило, действующее по умолчанию может быть изменено с помощью defaultpen(pen). Функция int fillrule(pen p=currentpen) возвращает правило заполнения. 6.5 Ïîëîæåíèå è íàïðàâëåíèå Позиция Relative(real) определяет место объекта по отношению к общей длине (arclength) пути. Имеются предопределенные положения объектов: position BeginPoint=Relative(0); position MidPoint=Relative(0.5); position EndPoint=Relative(1); Направление на рисунках удобно задавать с помощью как абсолютного направления по компасу, так и относительного направления Relative(pair), отсчитываемого от северной оси до локального направления пути. Предопределенные «компасные» направления показаны на рис. 6.3. NNW N NNE NW NE WNW ENE W E WSW ESE SW SE SSW S SSE Рис. 6.3. Предопределенные «компасные» направления. Эти компасные направления имеют следующий смысл: E=(1,0), N=(0,1), NE=unit(N+E), ENE=unit(E+NE). Они вместе с направлениями up, down, right и left определены в виде пар Глава 6. Основные инструменты 61 в основном модуле Asymptote plain (пользователь, который ввел локальную переменную E может использовать компасное направление E, предваряя его именем модуля, в котором оно определено: plain.E). Для удобства введены направления LeftSide, Center и RightSide как Relative(W), Relative((0,0)) и Relative(E), соответственно. Умножение LeftSide, Center, RightSide на коэффициент смещает объект относительно пути. 6.6 Ôðåéìû è êàðòèíêè 6.6.1 Ôðåéìû frame Фрейм frame – это холст для рисования в координатах PostScript. Впрочем, работа с фреймами по созданию отложенных процедур рисования не так удобна, как с картинками (picture), поэтому последние используются чаще. Неявным инициализатором фрейма является newframe. Функция bool empty(frame f) возвращает true только для пустого фрейма f. Процедура erase(frame) удаляет фрейм. Функции pair min(frame) и pair max(frame) возвращают координаты левого нижнего и правого верхнего углов, соответственно, ограничивающего фрейм прямоугольника. Содержимое фрейма src может быть нарисовано поверх фрейма dest процедурой void add(frame dest, frame src); или нарисовано под фреймом dest с помощью процедуры void prepend(frame dest, frame src); Смещение фрейма f в направлении align, подобно выравниванию метки (о чем будет идти речь дальше), достигается применением функции frame align(frame f, pair align); Чтобы нарисовать заполненный прямоугольник или эллипс вокруг фрейма или метки, и получить границу в виде пути, используют одну из предопределенных envelope-процедур: path box(frame f, Label L="", real xmargin=0, real ymargin=xmargin, pen p=currentpen, filltype filltype=NoFill, bool above=true); path roundbox(frame f, Label L="", real xmargin=0, real ymargin=xmargin, pen p=currentpen, filltype filltype=NoFill, bool above=true); path ellipse(frame f, Label L="", real xmargin=0, real ymargin=xmargin, pen p=currentpen, filltype filltype=NoFill, bool above=true); 6.6.2 Êàðòèíêè picture Картинка picture является структурой высокого уровня, определенной в модуле plain, и обеспечивает рисование на холсте в пользовательских координатах. По умолчанию рисование осуществляется на картинке currentpicture. Новая картинка создается так: picture pic; Анонимные картинки вводят с помощью выражения new picture. Процедура size задает требуемые размеры картинки: Глава 6. Основные инструменты 62 void size(picture pic=currentpicture, real x, real y=x, bool keepAspect=Aspect); Если оба размера x и y равны 0, пользовательские координаты будут интерпретированы как PostScript-координаты. В этом случае преобразованием, переводящим картинку в выходной формат, будет identity(). Если равен нулю только один из размеров, x или y, то в этом направлении никакие ограничения на размер не накладываются; это направление будет масштабироваться так же, как и другое направление. Если keepAspect равен Aspect или true, то картинка будет масштабироваться с сохранением пропорций, причем результирующая ширина не будет превышать x, а результирующая высота – y. Если keepAspect равно IgnoreAspect или false, картинка будет масштабироваться в обоих направлениях без сохранения пропорций, а ее результирующие ширина и высота будут равны, соответственно, x и y. Чтобы сделать пользовательские координаты для картинки pic кратными x в направлении Ox и кратными y в направлении Oy, применяется функция void unitsize(picture pic=currentpicture, real x, real y=x); При ненулевых значениях эти x и y перекрывают соответствующие размерные параметры картинки pic. Процедура void size(picture pic=currentpicture, real xsize, real ysize, pair min, pair max); преобразует картинку, масштабируя ее к пользовательскому координатному прямоугольнику box(min,max) в область ширины xsize и высоты ysize (если эти параметры ненулевые). Вместо этого, вызывая процедуру transform fixedscaling(picture pic=currentpicture, pair min, pair max, pen p=nullpen, bool warn=false); можно привести картинку к фиксированному масштабу в пользовательском координатном прямоугольнике box(min,max) и к уже заданному размеру картинки, учитывая толщину пера p. Будет выдано сообщение, если результирующий размер картинки превысит заданный размер. Чтобы картинку pic заключить в рамку и вывести в файл prefix.format, где format – один из форматов изображений, следует обратиться к функции shipout: void shipout(string prefix=defaultfilename, picture pic=currentpicture, orientation orientation=orientation, string format="", bool wait=false, bool view=true, string options="", string script="", light light=currentlight, projection P=currentprojection) Параметры options, script и projection имеют смысл только для трехмерных изображений. Если defaultfilename является пустой строкой, системой будет использован префикс outprefix(). Вообще говоря, процедура shipout() всегда неявно добавляется в файловый вывод, если только эта процедура не была указана явно. По умолчанию ориентация страницы предполагается Portrait, но это можно изменить с помощью переменной orientation. Чтобы вывести Глава 6. Основные инструменты 63 страницу в альбомной ориентации, достаточно указать orientation=Landscape или выполнить процедуру shipout(Landscape); Чтобы повернуть страницу на −90◦ , следует использовать ориентацию Seascape. Ориентация UpsideDown поворачивает страницу на 180◦ . Картинку можно явным образом заключить в рамку, если сделать вызов frame pic.fit(real xsize=pic.xsize, real ysize=pic.ysize, bool keepAspect=pic.keepAspect); Размеры и пропорции по умолчанию берутся такими, какими их задает процедура size (для которой по умолчанию размеры есть 0 и 0, а сохранение пропорций имеет значение true). Преобразование, которое будет выполняться над картинкой pic с целью заключить ее в рамку, возвращается функцией-членом pic.calculateTransform(). Чтобы нарисовать вокруг картинки рамку с полями, следует поместить картинку во фрейм bbox с помощью функции frame bbox(picture pic=currentpicture, real xmargin=0, real ymargin=xmargin, pen p=currentpen, filltype filltype=NoFill); Например, чтобы нарисовать рамку с полями величиной 0,25 см, достаточно применить процедуру shipout(bbox(0.25cm));. Рамка может быть снабжена фоном цвета p, если вызвать функцию function bbox(p,Fill). Функции pair min(picture pic, user=false); pair max(picture pic, user=false); pair size(picture pic, user=false); вычисляют границы и размер картинки pic, которые бы она имела, если бы была вставлена во фрейм со своими размерами по умолчанию. Если user=false, то результат возвращается в координатах PostScript, в противном случае – в пользовательских координатах. Функция pair point(picture pic=currentpicture, pair dir, bool user=true); предоставляет удобный способ определения точки на рамке картинки pic в направлении dir относительно ее центра при игнорировании поправок, вносимых объектами фиксированного размера (типа меток или стрелок). Если user=false, то результат возвращается в координатах PostScript, в противном случае – в пользовательских координатах. Функция pair truepoint(picture pic=currentpicture, pair dir, bool user=true); идентична функции point, только теперь в расчет берутся и все объекты фиксированных размеров. Если user=false, то результат возвращается в координатах PostScript, в противном случае – в пользовательских координатах. ×àñòü II Ðèñîâàíèå íà ïëîñêîñòè 64 Ãëàâà 7 Ìîäóëü plain Этот модуль является встроенным и не требует специального подключения при работе. Он содержит все основные процедуры и опции рисования на плоскости. 7.1 Ïðîöåäóðû dot è label Процедура label выполняет надписи на рисунке. Ее прототип имеет следующий вид: void label(picture pic = currentpicture, Label L, pair position, align align = NoAlign, pen p = nullpen, filltype filltype = NoFill); Если align=NoAlign, то метка будет центрирована в позиции position пользовательских координат, в противном случае она будет сдвинута в направлении align от точки position и смещена на PostScript-расстояние align*labelmargin(p). Label L означает метку, которая может быть строкой или структурой, полученной одной из функций: Label Label(string s="",pair position, align align=NoAlign, pen p=nullpen, embed embed=Rotate, filltype filltype=NoFill); Label Label(string s="", align align=NoAlign, pen p=nullpen, embed embed=Rotate, filltype filltype=NoFill); Label Label(Label L, pair position, align align=NoAlign, pen p=nullpen, embed embed=L.embed, filltype filltype=NoFill); Label Label(Label L, align align=NoAlign, pen p=nullpen, embed embed=L.embed, filltype filltype=NoFill); Параметр s задает текст метки. Параметр position указывает точку привязки метки. Равенство pen=nullpen означает, что используется перо, которое объявлено в Label; по умолчанию – currentpen. Текст метки может быть масштабирован, наклонен, сдвинут или повернут с помощью произведения соответствующих аффинных операторов. Например, rotate(45)*xscale(2)*L сначала масштабирует L по x, а затем поворачивает против часовой стрелки на 45 градусов. Окончательная позиция метки может перемещаться с помощью PostScript-преобразования координат: shift(10,0)*L. Аргумент embed определяет, как будет преобразовываться метка вместе со встраиваемой картинкой: Shift – только сдвиг вместе со встраиваемой картинкой; 65 Глава 7. Модуль plain Rotate 66 – лишь сдвиг и вращение (по умолчанию); Rotate(pair z) – поворот с вектором z; Slant – только сдвиг, поворот, наклон и отражение; Scale – сдвиг, поворот, наклон, отражение и масштабирование. Параметр filltype указывает тип заполнения прямоугольника, содержащего метку, (фона метки). Снабжение метками путей см. в п. 7.3 Процедура dot из модуля plain рисует точку в виде круга диаметром, равным толщине пера, используя указанный filltype (при этом толщина линии умножается на dotfactor, по умолчанию равный 6): void dot(picture pic=currentpicture, pair z, pen p=currentpen, filltype filltype=Fill); Обязательным является лишь параметр z, задающий координаты точки. Если к точке требуется добавить метку, используется процедура dot(picture pic=currentpicture, Label L, pair z, align align=NoAlign, string format=defaultformat, pen p=currentpen, filltype filltype=Fill); О рисовании точки в каждом узле пути см. п. 7.3. В примере 7.1 показаны различные способы изображения точек; точек, снабженных метками; и просто меток. Наипростейший оператор изображает черную точку. Если в аргументы добавить слово Label, то рядом с точкой появится метка с координатами точки. Красная точка снабжена красной же меткой, расположенной в направлении W (на запад). Синяя точка увеличена в размере и снабжена укрупненной сиреневой меткой, сдвинутой на северо-восток в 3 раза дальше, чем обычно. В правом столбце расположены метки, нарисованные сами по себе, без привязки к dot. Метки изображены разными цветами, одна из них повернута на −30◦ , для других использовалась опция filltype, которая создавала для метки фон, или рамку, или то и другое. Ïðèìåð 7.1. Òî÷êè è ìåòêè. settings.outformat="pdf"; settings.prc=false; size(0,5.5cm); dot((0,0)); dot(Label,(0,1)); pair A=(0,2); dot(A,L=Label("A"),W,red); pair B=(0,3); dot(B,L=Label(scale(2)*"B",magenta),3*NE,blue+8bp); label("This is label",(4,3),filltype=Fill(green)); label(rotate(-30)*"$A_3$",(4,1.5),brown); label("This is label too",(4,0), filltype=FillDraw(2mm,2mm,yellow,red)); label("This is label else",(2.7,-1.5), filltype=Draw(1mm,1mm,red),blue); shipout(bbox(0.2cm+invisible)); B A This is label A3 (0,1) This is label too This is label else В дальнейших текстах программ первая, вторая и последняя строчка будут опускаться. Глава 7. Модуль plain 67 7.2 Êðèâûå Áåçüå Для плавного соединения точек Asymptote использует кривые Безье. Для каждого внутреннего узла кубического сплайна может быть указано два направления (dir), входное и выходное: пара dir определяет направление входящей или исходящей касательной, соответственно, к кривой в этом узле. Для концевых узлов такое направление задается только с внутренней стороны. Кубический сплайн между узлом z0 с постопорной точкой c0 и узлом z1 с предопорной точкой c1 имеет вид кривой Безье (1 − t)3 z0 + 3t(1 − t)2 c0 + 3t2 (1 − t)c1 + t3 z1 , 0 ≤ t ≤ 1. Как показано на рис. 7.1, средняяя точка третьего порядка m5 , полученная из двух концевых точек z0 и z1 и двух опорных точек c0 и c1 , является точкой, которой соответствует t = 1/2 на кривой Безье, сформированной четверкой (z0 , c0 , c1 , z1 ). Это позволяет рекурсивно строить требуемую кривую, используя полученную среднюю точку 3-го порядка как концевую и соответствующие средние точки 2-го и 1-го порядков в качестве опорных. c0 m3 m1 m5 m0 c1 m4 m2 z0 z1 Рис. 7.1. Кривая Безье. На рис. 7.1 m0 , m1 и m2 – средние точки 1-го порядка, m3 и m4 – 2-го порядка, а m5 – средняя точка 3-го порядка. Кривая строится рекурсивно с применением алгоритма к четверке (z0 , m0 , m3 , m5 ) и (m5 , m4 , m2 , z1 ). На самом деле аналогичное свойство имеет место и для точек, расположенных в любой доле сегмента [0; 1], не только для средних точек (t = 1/2). Сконструированная таким образом кривая Безье обладает следующими свойствами: • целиком содержится в выпуклой оболочке заданных четырех точек; • начинается из первой концевой точки и идет к первой опорной, а заканчивается, идя из второй опорной точки к второй концевой точке. Тем не менее гораздо удобнее использовать оператор .. , который разрешает Asymptote самой выбрать опорные точки, используя алгоритм, описанный Дональдом Кнутом в 14 главе его книги «Все про METAFONT». Впрочем, пользователь может модифицировать такие параметры кривой как направление (direction), натяжение (tension) и загиб (curl). Чем больше натяжение, тем прямее кривая, тем лучше она аппроксимируется прямой линией. Натяжение можно изменять от 1, его значения по умолчанию, до 0.75. (см. John D. Hobby, Discrete and Computational Geometry 1, 1986). Параметр curl определяет загиб в конечных точках кривой: 0 означает прямую, а 1, значению по умолчанию, приближенно соответствует окружность. Глава 7. Модуль plain 68 Коннектор MetaPost’а ... , который отличается тем, что, когда это возможно, пытается ограничить путь некоторым треугольником, определяемым конечными точками и направлениями, представлен в Asymptote аббревиатурой :: for ..tension atleast 1 .. (многоточие занято в Asymptote под rest-аргументы). Коннектор --- является сокращением для ..tension atleast infinity.., а коннектор & соединяет два пути, начиная с последнего узла первого пути (он, как правило, должен присоединиться к первому узлу второго пути). 7.3 Ïðîöåäóðû path, guide è draw Путь path может быть элементарным объектом, например, точкой, а может быть совокупностью объектов, в том числе других путей, соединенных между собой различными способами. Неявным представителем пути является nullpath. Пусть, p и q – два пути. Тогда p--q – пути соединяются отрезком прямой, p..q – пути соединяются кубическим сплайном, p^^q – пути рассматриваются как один путь (например, в случае разрывных путей), p::q – пути соединяются отрезком прямой, плавно переходящим в следующий путь, p---q – «натянутая» линия, p & q – объединение двух независимых путей. Команда cycle соединяет конечную и начальную точки пути. Для присоединения метки к пути используют процедуру label следующей структуры: void label(picture pic=currentpicture, Label L, path g, align align=NoAlign, pen p=currentpen, filltype filltype=NoFill); Как уже указывалось, процедура guide строит нерешенный кубический сплайн (список узлов и опорных точек сплайна). Неявным инициализатором является nullpath.guide. Процедура похожа на path, но вычисление кубического сплайна откладывается до времени рисования (когда он будет преобразован в path). Процедура draw собственно и выполняет рисование. Ее определение таково: draw(picture pic=currentpicture, Label L="", path g, align align=NoAlign, pen p=currentpen, arrowbar arrow=None, arrowbar bar=None, margin margin=NoMargin, Label legend="", marker marker=nomarker); Она рисует путь g на картинке pic, пользуясь пером pen. Путь может сопровождаться меткой L, выровненной с помощью align; стрелками, снабженными черточками на концах; полями, обозначениями и маркерами. Обязательным параметром является лишь путь, path. Черточки могут быть полезны для указания размеров. Они принимают значения None, BeginBar, EndBar (что равносильно Bar) и Bars (черточки изображаются на обоих концах пути). Каждая черточка (за исключением None) может иметь дополнительный действительный аргумент, который имеет смысл длины черточки в PostScript-координатах. По умолчанию эта длина равна barsize(pen). Возможные значения стрелки (arrow): None, Blank (стрелки на пути не изображаются), BeginArrow, MidArrow, EndArrow (что равносильно Arrow) и Arrows (стрелки изображаются на обоих концах пути). Для каждого вида стрелок, кроме None и Blank можно указать тип острия (DefaultHead, SimpleHead, HookHead, TeXHead), размер size (в PosrScript-координатах), угол angle (в градусах), тип заполнения filltype (FillDraw, Fill, NoFill, UnFill, Глава 7. Модуль plain 69 Draw) и (кроме MidArrow и Arrows) позицию position на пути в смысле значения функции point(path p,real t). По умолчанию размер стрелки, которая создается пером p, равен arrowsize(p). Можно использовать стрелки для дуг: BeginArcArrow, EndArcArrow (что равносильно ArcArrow), MidArcArrow и ArcArrows). Поля margin применяют для сокращения видимой части пути labelmargin(p), чтобы избежать наложений на другие объекты. Значениями margin являются NoMargin, BeginMargin, EndMargin (что равносильно Margin) и Margins (поля возникают на обоих концах пути). Описание и применение опций legend и marker рассматривается в главе о модуле graph при описании построения графиков функций. Процедуру draw можно использовать для рисования точки. Для этого строят путь, состоящий из точки. Рассмотрим рисунки, демонстрирующие некоторые из перечисленных возможностей. На рис. примера 7.2 показаны все виды соединения путей, кроме ^^. Ïðèìåð 7.2. Ñîåäèíåíèÿ ïóòåé. z0 size(2.7cm,0); pair z0=(2,12),z1=(0,10),z2=(0,9),z3=(2,8),z4=(0,7),z5=(0,6), z6=(2,4),z7=(0,3),z8=(0,2),z9=(2,0); path connections = z0..z1---z2::{right}z3 & z3..z4--z5::{right}z6 & z6::z7---z8..{right}z9; z1 draw(connections,blue+bp); z4 dot(z0,L=Label("$z_0$",black),E,red); dot(z1,L=Label("$z_1$",black),W,red); dot(z2,L=Label("$z_2$",black),W,red); dot(z3,L=Label("$z_3$",black),E,red); dot(z4,L=Label("$z_4$",black),W,red); dot(z5,L=Label("$z_5$",black),W,red); dot(z6,L=Label("$z_6$",black),E,red); dot(z7,L=Label("$z_7$",black),W,red); dot(z8,L=Label("$z_8$",black),W,red); dot(z9,L=Label("$z_9$",black),E,red); z5 z2 z3 z6 z7 z8 z9 Соединив определенные точки коннектором .. в путь и использовав cycle, можно получить единичную окружность, на которой можно нарисовать эти точки и снабдить их метками. Рисунок примера 7.3, кроме этого, показывает, что Asymptote автоматически расставляет метки вдоль окружности, сама выбирая их расположение так, чтобы они не попадали на окружность и вообще располагались вдоль нее регулярным образом. В примере 7.21 представлен вариант автоматической расстановки меток, когда метки и точки окрашиваются в разные цвета. В примере 7.3 был использован вариант процедуры dot, который рисует точку в каждом узле пути: void dot(picture pic=currentpicture, Label[] L=new Label[], path g, align align=RightSide, string format=defaultformat, pen p=currentpen, filltype filltype=Fill); Глава 7. Модуль plain 70 Ïðèìåð 7.3. Ðàññòàíîâêà òî÷åê è ìåòîê íà êðèâîé. size(3.5cm,0); pair A=(1,0),B=(0,1),C=(-1,0),D=(0,-1); path unitcircle = A..B..C..D..cycle; draw(unitcircle,blue); Label[] Labels=new Label[4]; Labels[0]="$A$"; Labels[1]="$B$"; Labels[2]="$C$"; Labels[3]="$D$"; dot(Labels,unitcircle,red); B C A D Рис. примера 7.4 демонстрирует, как формируется путь-треугольник, определяются векторы направления его сторон и используются повороты, заполнения, указания позиций и выравнивание для подходящего расположения меток. Ïðèìåð 7.4. Ìåòêè ïèôàãîðîâà òðåóãîëüíèêà. size(3.5cm,0); pair A=(0,0), B=(2,3), C=(2,0); path treug= A--B--C--cycle, f=A--C, g=C--B, h=A--B; label("$A$",A,dir(A-C)); label("$B$",B,dir(B-A)); label("$C$",C,dir(C-A)); A b=2 a=3 c= √ 13 draw(Label("$b=2$",position=MidPoint,brown),f,blue); draw(Label("$a=3$",embed=Rotate(dir(g)),brown),g,blue); draw(Label("$c=\sqrt{13}$",embed=Rotate(dir(h)), align=Center,brown,filltype=UnFill),h,blue); B C Как уже было сказано, в арсенале Asymptote имеются различного типа стрелки, форму и размещение которых пользователь может варьировать в зависимости от своих целей. На рис. примера 7.5 на синей кривой изображена стрелка по умолчанию; чтобы ее задать, надо только написать Arrow. Для зеленой стрелки задана ее величина в мм, а ее внутренность ничем не заполнена. Красная кривая снабжена двумя стрелками на концах (Arrows) и имеет отличный от стандартного тип TeXHead. На розовой кривой в ее начале и конце имеются черточки, а стрелка помещена в середине пути, изображена синим цветом, отличным от цвета кривой, и имеет тип HookHead. Оливковая кривая заканчивается стрелкой SimpleHead, а на сиреневой кривой расстояние от ее начала до стрелки составляет 0.8 от длины кривой; причем, стрелка имеет двойной тип: с одной стороны, она заявлена как HookHead, а с другой, относится к семейству стрелок, предназначенных для снабжения ими дуг окружностей. Как видим, это не мешает ее использовать для произвольных кривых. Следующие семь кривых демонстрируют типы линий в Asymptote: сплошные, штриховые, пунктирные и штрих-пунктирные с различной величины штрихами. Первая кривая снабжена метками L1 и L2 . Тем самым показано, что путь тоже может быть помечен в соответствии с еще одним вариантом оператора label: void label(picture pic=currentpicture, Label L, path g, align align=NoAlign, pen p=nullpen, filltype filltype=NoFill); По умолчанию метка размещается в середине пути, как метка L2 на рис. 7.5. Альтернативое положение метки (в смысле point(path p, real t)) может быть указано с помощью Глава 7. Модуль plain 71 Ïðèìåð 7.5. Òèïû ñòðåëîê è ëèíèé. size(5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); L1 L2 path curv=(0,0)..(1,0.5)..(2,0)..(3,0.5); label(Label("$L_1$",Relative(0.25)),curv,S); label("$L_2$",curv,NE); draw(curv,blue,Arrow); draw(shift(0,-0.6)*curv,heavygreen,BeginArrow(5mm,Draw), Bar); draw(shift(0,-1.2)*curv,8*red,Arrows(TeXHead)); draw(shift(0,-1.8)*curv,bp+magenta,MidArrow(HookHead, Fill(blue)),Bars(2mm)); draw(shift(0,-2.4)*curv,lightolive,Arrow(SimpleHead)); draw(shift(0,-3)*curv,purple,ArcArrow(HookHead, Relative(0.8))); draw(shift(0,-3.6)*curv,heavycyan); draw(shift(0,-4.2)*curv,blue+dashed); draw(shift(0,-4.8)*curv,darkgreen+black+dotted); draw(shift(0,-5.4)*curv,red+longdashed); draw(shift(0,-6)*curv,heavymagenta+dashdotted); draw(shift(0,-6.6)*curv,olive+longdashdotted); pen Dotted(pen p=currentpen) {return linetype(new real[] {0,3})+2*linewidth(p);} pen Dotted=Dotted(); draw(shift(0,-7.2)*curv,purple+Dotted); position в конструкции Label. Позиция Relative(real) определяет относительное положение метки по отношению к длине всего пути. Так метка L2 на рис. примера 7.5 находится на расстоянии четверти пути от его начала. Для удобства предопределены следующие аббревиатуры: position BeginPoint=Relative(0); position MidPoint=Relative(0.5); position EndPoint=Relative(1); Метки пути выравниваются вдоль направления align, которое может определяться абсолютным компасным направлением (pair) или направлением Relative(pair), отсчитываемым от северной оси до локального направления пути. Для удобства LeftSide, Center и RightSide определяются как Relative(W), Relative((0,0)) и Relative(E), соответственно. Умножение LeftSide, Center, RightSide слева на масштабирующий множитель передвигает метку ближе к пути или дальше от него. Метка со стрелкой фиксированного размера длины arrowlength, указывающая на b в направлении dir, может быть получена применением процедуры void arrow(picture pic=currentpicture, Label L="", pair b, pair dir, real length=arrowlength, align align=NoAlign, pen p=currentpen, arrowbar arrow=Arrow, margin margin=EndMargin); Глава 7. Модуль plain 72 Если выравнивание не указано (в Label или в виде явного аргумента), опциональная Label будет выровнена в направлении dir с использованием границы margin. На базе рассмотренных операторов можно сделать несложный геометрический чертеж, рис. примера 7.6, или план квартиры, см. Приложение A. В последнем примере можно познакомиться с тем, как используется коннектор ^^ и чем его использование отличается от применения коннектора &. Ïðèìåð 7.6. Ïðèçìà. size(6.5cm,0); pair A=(-2,-0.5), B=(-0.8,0.5), C=(2,0.5), D=(0.8,-0.5), SS=(0,3), O=(0,0); draw(A--SS--C--D--cycle,blue); draw(SS--D,blue); draw(A--C--B--cycle,blue+dashed); draw(D--B--SS--O--B,blue+dashed); dot(O,L=Label("$O$",black),1.5*S,red); label("$A$",A,W); label("$B$",B,NW); label("$C$",C,E); label("$D$",D,ESE); label("$S$",SS,N); label("$l$",SS--C,NE); label("$a$",A--D,S); label(L=Label("$h$",Relative(0.6)),SS--O); S l h B A C O a D Типы path и guide похожи, но последний путь окончательно вычисляется непосредственно перед рисованием, что дает возможность провести более гладкую кривую. В примере 7.7 сплошная кривая как guide формирует в цикле свои узлы и опорные точки, но рисуется лишь по окончании цикла. Штриховая кривая как path в цикле делает то же самое, но дополнительно еще пристыковывает каждый новый сегмент пути к предыдущему, так что по окончании цикла получаем сформированную кривую, которая, однако, выглядит менее гладко, чем сплошная кривая. Можно сказать, что path строит кривую локально, а guide – глобально. Ïðèìåð 7.7. Ðàçëè÷èÿ ìåæäó path è guide. size(9cm,0); real mexican(real x) {return (1-8x^2)*exp(-(4x^2));} int n=30; real a=1.5; real width=2a/n; guide hat; path solved; for(int i=0; i < n; ++i) { real t=-a+i*width; pair z=(t,mexican(t)); hat=hat..z; solved=solved..z; } draw(hat); dot(hat,red); draw(solved,dashed); Еще одна особенность отличает path от guide. Фрагмент программы guide g; for(int i=0; i < 10; ++i) Глава 7. Модуль plain 73 g=g--(i,i); path p=g; выполняется за линейное время, а фрагмент path p; for(int i=0; i < 10; ++i) p=p--(i,i); – за квадратичное. Рассмотрим влияние параметров direction, tension и curl на форму кривых. В примере 7.8 показано, как зависит форма кривой от параметра direction. Этот параметр определяет направление касательной в заданной точке кривой, причем, его значение записывается в фигурных скобках. Если параметр стоит перед точкой, он указывает направление левой касательной, если после точки, то правой касательной. В примере такому заданию направления касательной соответствует кривая, проходящая через точки A, B и C. Для следующей кривой, проходящей через точки D, E и F, направления касательных задаются с помощью векторов up = (0,1), down = (-1,0), left = (-1,0) и right = (1,0). Еще одна возможность – задавать упомянутые направления в градусах, как это сделано для третьей кривой, проходящей через точки G, H и I. Ïðèìåð 7.8. Ïàðàìåòð direction. size(6cm,0); pair A=(0,0), B=(2,1), C=(3,0); path p1=A{(1,1)}..{(1,0)}B{0,-1}..{(1,.5)}C; draw(p1,blue); dot(p1,red); pair D=(0,-1.5), E=(1.5,-0.5), F=(2.5,-1.5); path p2=D{up}..{right}E{down}..{left}F; draw(p2,blue); dot(p2,red); pair G=(0,-3), H=(2,-2), I=(3,-3); path p3=G{dir(75)}..{dir(45)}H{dir(0)}..{dir(-90)}I; draw(p3,blue); dot(p3,red); Параметр tension (натяжение) регулирует кривизну линии. Чем он больше, тем сильнее кривая похожа на прямую. Значение натяжения по умолчанию равно 1, минимальное значение равно 0.75. В примере 7.9 первая кривая регулируется одним значением натяжения между узлами сплайна, вторая – двумя. Параметр curl (загиб) регулирует кривизну кривой в конечных точках пути. Значение загиба 0 приближает кривизну линии к прямой, значение 1, принятое по умолчанию, – к окружности. В примере 7.10 показано использование этого параметра. Глава 7. Модуль plain 74 Ïðèìåð 7.9. Ïàðàìåòð tension. size(5cm,0); pair A=(0,0), B=(2,1), C=(3,0); path p1=A..tension 0.75 ..B..tension 2 ..C; draw(p1,blue); dot(p1,red); pair D=(0,-1.5), E=(2,-0.5), F=(3,-1.5); path p2=D..tension 2 and 0.75 ..E..tension 0.9 and 13 ..F; draw(p2,blue); dot(p2,red); Ïðèìåð 7.10. Ïàðàìåòð curl. size(5cm,0); pair A=(0,0), B=(2,1), C=(3,0); path p1=A{curl 0}..B..{curl 0}C; draw(p1,blue); dot(p1,red); pair D=(0,-1.5), E=(2,-0.5), F=(3,-1.5); path p2=D{curl 1}..E..{curl 0}F; draw(p2,blue); dot(p2,red); 7.4 Ïðîöåäóðû unitcircle, circle, ellipse è arc Путь path unitcircle; представляет собой окружность с центром в начале координат. Процедура path circle(pair c, real r); возвращает окружность радиуса r с центром в точке c. Процедура path ellipse(pair c, real a, real b); производит эллипс с центром в точке c, имеющий горизонтальную полуось a и вертикальную полуось b. Построение окружностей и эллипса приведено в примере 13.18. Процедура path arc(pair c, explicit pair z1, explicit pair z2, bool direction=CCW); создает дугу с центром в точке c, которая идет из точки z1 в точку z2. По умолчанию дуга проводится против часовой стрелки: direction=CCW (по умолчанию). Если требуется провести дугу в противоположном направлении, следует набрать direction=CW. Рисунок примера 13.19 выполнен с помощью именно этой процедуры. Есть еще один вариант задания дуги реализует процедура path arc(pair c, real r, real angle1, real angle2); Глава 7. Модуль plain 75 Ïðèìåð 7.11. Îêðóæíîñòü è ýëëèïñ. size(4cm,0); dot((0,0)); draw(unitcircle,red); draw(circle((0,0),2),blue); draw(ellipse((0,0),2,1),green); Ïðèìåð 7.12. Äóãà èç òî÷êè â òî÷êó. size(5.5cm,0); pair M=(-1,-1), N=(-1,1), P=(1,1), Q=(1,-1), A=(M+N)/2, B=(N+P)/2, C=(P+Q)/2, D=(Q+M)/2; draw(M--N--P--Q--cycle,blue); draw(unitcircle,green); draw(A--C,dashed+blue); draw(B--D,dashed+blue); draw(arc(D,Q,M),red); draw(arc(A,M,N),red); draw(arc(B,N,P),red); draw(arc(C,P,Q),red); dot(A,L=Label("$A$",black),left,red); dot(B,L=Label("$B$",black),up,red); dot(C,L=Label("$C$",black),right,red); dot(D,L=Label("$D$",black),down,red); B A C D которая формирует дугу радиуса r с центром в точке c, заключенную между углами angle1 и angle2. Если angle1 < angle2, дуга проводится против часовой стрелки, в противном случае – по часовой стрелке. Пример 13.20 демонстрирует применение этой процедуры, а заодно и рисование не простых дуг, а направленных, снабженных на концах стрелками. 7.5 Ïðîöåäóðû unitsquare, box è polygon Чтобы упростить вычерчивание многоугольников, в Asymptote имеются специальные процедуры. Одна з них path unitsquare; создает единичный квадрат, левая нижняя вершина которого находится в начале координат. Процедура path box(pair a, pair b); возвращает прямоугольник, в котором противоположными вершинами являются a и b. Наконец, процедура path polygon(int n); формирует правильный n-угольник, вписывающийся в единичный круг. Пример 7.14 демонстрирует эти возможности. Глава 7. Модуль plain 76 Ïðèìåð 7.13. Äóãà îò óãëà äî óãëà. y 120 ◦ ◦ 90 x O − 90 ◦ size(5.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); draw((-1.3,0)--(1.3,0),orange,Arrow(size=2mm), L=Label("$x$",position=EndPoint, align=right,mahogany)); draw((0,-1.3)--(0,1.3),orange,Arrow(size=2mm), L=Label("$y$",position=EndPoint, align=up,mahogany)); draw(circle((0,0),1.1),blue); draw(arc((0,0),0.3,0,90),arrow=Arrow(2mm), L=Label(rotate(-45)*"$90^\circ$", position=MidPoint,black),green); draw(arc((0,0),0.7,0,120),arrow=Arrow(2mm), L=Label(rotate(-27)*"$120^\circ$", position=MidPoint,black),green); draw(arc((0,0),0.5,0,-90),arrow=Arrow(2mm), L=Label(rotate(45)*"$-90^\circ$", position=MidPoint,align=SE,black),green); label("$O$",(0,0),SW,mahogany); draw((0,0)--(-0.55,sqrt(1.1^2-0.55^2)),mahogany+dashed); Ïðèìåð 7.14. Ìíîãîóãîëüíèêè. size(7.3cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); draw(unitsquare,blue); draw(box((-0.2,-1.2),(5.8,5.66)),red); dot((0,0),red); for (int n = 3; n <= 8; ++n){ real s = (n < 6) ? 2.3 : 4.6; int m = (n < 6) ? n : n-3; draw(shift((s,(m-3)*2.2))*polygon(n),blue); draw(shift((s,(m-3)*2.2))*unitcircle,green); } 7.6 Çàïîëíåíèå, ãðàäèåíòíàÿ çàëèâêà, îáðåçêà Внутренность циклического пути может быть заполнена каким-нибудь цветом. Это оформление выполняет процедура void fill(picture pic=currentpicture, path g, pen p=currentpen); Более гибкая процедура void filldraw(picture pic=currentpicture, path g, pen fillpen=currentpen, pen drawpen=currentpen); позволяет не только заполнить область пером fillpen, но и обвести ее контуром, цвет которого задает drawpen. Так что область и ее граница могут быть разного цвета. Глава 7. Модуль plain 77 Следующая процедура void filloutside(picture pic=currentpicture, path g, pen p=currentpen); заполняет область вне циклического пути p, между ним и границами рисунка. Результат использования процедур можно видеть на рис. примера 7.15. Ïðèìåð 7.15. Çàïîëíåíèå. size(4.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); filloutside(rotate(14)*scale(1.5)*polygon(9),green); filldraw(unitcircle,fillpen=pink,drawpen=blue+bp); fill((0,-0.5){curl 0}..(-0.5,0.25)..(-0.25,0.5).. {dir(-45)}(0,0.25){dir(45)}..(0.25,0.5)..(0.5,0.25).. {curl 0}cycle,red); Теперь рассмотрим градиентные заливки. Процедура void latticeshade(picture pic=currentpicture, path g, bool stroke=false, pen fillrule=currentpen, pen[][] p) обеспечивает плавный переход цветов, заданных массивом p, для заливки области, ограниченной контуром g, с учетом правила заполнения fillrule. Если параметр stroke=true, заливка производится точно так же, как при выполнении процедуры draw(pic,g,fillrule+zerowinding); в этом случае цикличность пути g не обязательна. Цвета в p должны быть из одного и того же цветового пространства. Чтобы перевести цвета в подходящее цветовое пространство, можно использовать функции rgb(pen) или cmyk(pen). Такой тип заливки показан в примере 7.16. Ïðèìåð 7.16. Ãðàäèåíòíàÿ çàëèâêà íàáîðîì öâåòîâ. size(4.5cm,0); pen[][] p={{white,grey,black}, {red,green,blue}, {cyan,magenta,yellow}}; latticeshade(unitsquare,p); Осевая градиентная заливка означает плавное перетекание одного цвета в другой в определенном направлении. Процедурв void axialshade(picture pic=currentpicture, path g, bool stroke=false, pen pena, pair a, bool extenda=true, pen penb, pair b, bool extendb=true); Глава 7. Модуль plain 78 создает такую заливку, причем, цвет pena постепенно переходит в цвет penb в направлении от точки a к точке b. Параметры extenda и extendb указывают на необходимость осуществлять процесс заливки за пределами концов оси a--b. Демонстрацию осевой заливки см. в примере 7.17. Ïðèìåð 7.17. Îñåâàÿ ãðàäèåíòíàÿ çàëèâêà. size(4.5cm,0); path g=box((0,0),(4,3)); axialshade(g,red,(0,1),yellow,(4,1)); Радиальная градиентная заливка осуществляет плавный переход одного цвета в другой вдоль отрезков радиусов, лежащих между двумя окружностями. Для этого имеется процедура void radialshade(picture pic=currentpicture, path g, bool stroke=false, pen pena, pair a, real ra, bool extenda=true, pen penb, pair b, real rb, bool extendb=true); которая начинает рисование цветом pena на окружности радиуса ra с центром в точке a, а заканчивает – цветом penb на окружности радиуса rb с центром в точке b. Радиальная заливка представлена в примере 7.18. Ïðèìåð 7.18. Ðàäèàëüíàÿ ãðàäèåíòíàÿ çàëèâêà. size(5cm,0); path g=scale(2)*unitcircle; label("$a \le r \le b$"); a≤r≤b radialshade(unitcircle^^g,yellow+evenodd,(0,0),1.0, yellow+brown,(0,0),2); Переходим к выполнению обрезки изображений. Для этого служит процедура void clip(picture pic=currentpicture, path g, stroke=false, pen fillrule=currentpen); которая обрезает текущее изображение по границе пути g, используя правило заполнения fillrule. Если параметр stroke=true, заливка равносильна рисованию области процедурой draw(pic,g,fillrule+zerowinding); в этом случае цикличность пути g не обязательна. Отметим, что данная процедура обрезает все, что было нарисовано до ее применения, оставляя без обрезки то, что будет нарисовано после. Так, в примере 7.19 обрезается розовый пятиугольник и не обрезается голубой, поскольку первый был вычерчен до применения процедуры clip, а второй – после. Глава 7. Модуль plain 79 Ïðèìåð 7.19. Ïðèìåíåíèå ïðîöåäóðû clip. size(5cm,0); filldraw(scale(1.5)*polygon(5),pink,red); clip(box((-1.5,-0.5),(4.7,1.6))); filldraw(shift((3.2,0))*scale(1.5)*polygon(5),cyan,blue); 7.7 Êàðòèíêè picture Все картинки, выполненные в примерах, были картинкой currentpicture. Но можно создавать и другие картинки, помещая их на текущей картинке в различных местах. Для этого следует объявить картинку как переменную типа picture, а затем рисовать на ней, не забывая добавлять в каждый из операторов название этой картинки. После завершения рисунка полученную картинку (pic1) можно разместить на текущей картинке currentpicture с помощью процедуры void add(picture pic1, bool group=true, filltype filltype=NoFill, bool above=true); или на любой другой картинке (pic2) – с помощью процедуры void add(picture pic2, picture pic1, bool group=true, filltype filltype=NoFill, bool above=true); Параметр group определяет, будет ли графический интерфейс xasy обрабатывать все элементы pic1 как одно целое. Параметр filltype назначает заполнение или обрезание картинки. Наконец, параметр above отвечает за рисование картинки поверх имеющихся объектов или под ними. Рисунок примера 7.20 демонстрирует создание рожицы в виде отдельной картинки, а затем ее размещение в различных местах основной картинки с масштабированием и поворотами. Ïðèìåð 7.20. Êàðòèíêè íà êàðòèíêå. size(5.5cm,0); pen p=blue+3bp; draw((-2,1)--(2,1)--(0,-3)--cycle,p); draw((0,-3)--(0,1),p); picture pic; filldraw(pic,unitcircle,paleyellow,blue); fill(pic,shift(-0.4,0.3)*scale(0.12)*unitcircle,red); fill(pic,shift(0.4,0.3)*scale(0.12)*unitcircle,red); fill(pic,shift(0,-0.1)*scale(0.2)*polygon(3),brown); draw(pic,arc((0,0),0.6,-45,-135),brown+2bp); add(shift(0,-0.4)*scale(0.6)*pic); add(rotate(180,(0,-3))*shift(0,-3)*scale(0.8)*pic); add(rotate(45,(-2,1))*shift(-2,1)*scale(0.5)*pic); add(rotate(-45,(2,1))*shift(2,1)*scale(0.5)*pic); Иногда возникает необходимость удалить содержимое картинки. Для этого следует применить функцию void erase(picture pic=currentpicture); Ометим, что при таком удалении размеры картинки сохраняются. Глава 7. Модуль plain 80 7.8 Ïîäïóòè è ïåðåñå÷åíèÿ Подпути вместе с массивами и точками пересечения являются важным инструментом для получения новых геометрических объектов на основе уже имеющихся. Имеется два варианта формирования подпути. Первый задается функцией path subpath(path p, int a, int b); которая возвращает подпуть пути p от узла сплайна a до узла b. Если a > b, то направление пути будет обратным. Использование этой функции показано в примере 7.21: красный подпуть является частью синего пути между вторым и четвертым узлом сплайна. Стрелка показывает направление прохождения подпути. Ïðèìåð 7.21. Ïîäïóòü ìåæäó óçëàìè ñïëàéíà. size(5cm,0); path g=(-2,-2)..(0,-1)..(2,-1.5)..(2,1.5).. (-2,2.5)..(0,0.5); 4 3 draw(g,blue); Label[] Labels=new Label[4]; 5 for (int i; i<=length(g); ++i) Labels[i]=Label(string(i),black); dot(Labels,g,green+5bp); draw(subpath(g,2,4),red+bp,Arrow(3mm)); 1 0 2 Второй способ выделения подпути осуществляет функция path subpath(path p, real a, real b); у которой аргументы a и b уже являются действительными числами и имеют смысл значений параметра, определяющего положение точки на пути p в смысле point(path, real). Если a > b, то направление подпути опять-таки будет обратным. Этот второй способ продемонстрирован на рисунке примера 7.22, в котором выделяется подпуть красного цвета от точки, которой отвечает значение упомянутого параметра, равное 0.7, до точки, которой соответствует значение параметра 3.5. Значение 0.7 означает 70% пути между узлами 0 и 1, а 3.5 отсчитывает 50% кривой между узлами 3 и 4. Рассмотрим процедуры, осуществляющие поиск точек пересечения путей. Простейшая из них pair extension(pair P, pair Q, pair p, pair q); находит точку пересечения прямых, на которых находятся отрезки P--Q и p--q. На рисунке примера 7.23 красным цветом показана точка пересечения продолжения двух отрезков, найденная с помощью этой процедуры. Однако и для пересекающихся отрезков точка пересечения может быть получена таким же способом, см. рис. примера 7.24. Для произвольных кривых какую-либо точку пересечения путей p и q отыскивает процедура pair intersectionpoint(path p, path q, real fuzz=-1); Глава 7. Модуль plain 81 Ïðèìåð 7.22. Ïîäïóòü ìåæäó ïðîèçâîëüíûìè òî÷êàìè. size(5cm,0); path g=(-2,-2)..(0,-1)..(2,-1.5)..(2,1.5)..(-2,2.5)..(0,0.5); draw(g,blue); Label[] Labels=new Label[4]; for (int i; i<=length(g); ++i) Labels[i]=Label(string(i),black); dot(Labels,g,green+5bp); path gg=subpath(g,0.7,3.5); draw(gg,red+bp,Arrow(3mm)); dot(relpoint(gg,0),red+5bp); 4 3 5 1 0 Ïðèìåð 7.23. Ïåðåñå÷åíèå ïðîäîëæåíèé îòðåçêîâ. size(5cm,0); pair E=(-2,3), F=(0,3), G=(-2,5), H=(0,4); draw(E--F^^G--H,blue); pair N=extension(E,F,G,H); dot(E--F^^G--H); draw(F--N,dashed+blue); draw(H--N,dashed+blue); dot(N,red+4bp); Ïðèìåð 7.24. Ïåðåñå÷åíèå îòðåçêîâ. size(5cm,0); pair A=(-2,-1), B=(2,1), C=(-2,2), D=(2,-1.5); draw(A--B^^C--D,blue); pair M=extension(A,B,C,D); dot(M,red+4bp); Ïðèìåð 7.25. Ïåðåñå÷åíèå ïðîèçâîëüíûõ êðèâûõ. size(6.5cm,0); path g=(-2,-1)..(0,1)..(2,-1)..(1,2)..(-1,2)..cycle; draw(g,blue); path h=(-3,2)..(0,-0.5)..(3,2); draw(h,red); path s=(-2.5,2)..(0,0.5)..(2.5,2); draw(s,green); pair GH=intersectionpoint(g,h); dot(GH,brown+3bp); pair[] GS=intersectionpoints(g,s); dot(GS,magenta+3bp); В примере 7.25 эта процедура находит точку пересечения синего и красного путей. Все точки пересечения путей p и q находит процедура 2 Глава 7. Модуль plain 82 pair[] intersectionpoints(path p, path q, real fuzz=-1); В примере 7.25 результатом ее работы являются точки пересечения синей и зеленой кривой. С помощью этой же процедуры можно найти и точки самопересечения кривой, при этом в их список попадают и узлы сплайна, см. пример 7.26. Ïðèìåð 7.26. Ñàìîïåðåñå÷åíèå êðèâîé. size(6.5cm,0); path p=(-2.5,-4)..(0,-1)..(2.5,-4)..(1,-2).. (-1,-2)..cycle; draw(p,blue); pair[] PP=intersectionpoints(p,p); dot(PP,red+3bp); Поскольку точки пересечения кривых заносятся в одномерный массив, из него можно извлекать только необходимые нам точки. В примере 7.27 из массива GS извлекаются и изображаются на рисунке только точки с индексами [0] и [2]. Ïðèìåð 7.27. Èçâëå÷åíèå òî÷åê ïåðåñå÷åíèÿ èç îäíîìåðíîãî ìàññèâà. size(6.5cm,0); path g=(-2,-1)..(0,1)..(2,-1)..(1,2)..(-1,2)..cycle; path s=(-2.5,2)..(0,0.5)..(2.5,2); draw(g,blue); draw(s,green); pair[] GS=intersectionpoints(g,s); dot(Label("GS[0]",black),GS[0],3*WSW,magenta+3bp); dot(Label("GS[2]",black),GS[2],2*E,magenta+3bp); GS[2] GS[0] Еще одна процедура real[] intersect(path p, path q, real fuzz=-1); формирует двухиндексный массив, элемент которого [0] содержит значение параметра для пути p, а элемент [1] – значение параметра для пути q, при которых эти пути пересекаются (в смысле point(path, real)). На рис. примера 7.28 точка P является точкой пересечения с индексом [1] для кривой p, причем, значение параметра для этой кривой равно PQ[1][0] (индекс [0] нумерует «первую» кривую, p). Точка Q является точкой пересечения с индексом [3] для кривой q, причем, значение параметра для этой кривой равно PQ[3][1] (индекс [1] нумерует «вторую» кривую, q). 7.9 Ôðàãìåíòû ïóòè Для выделения фрагментов (slice) путей используются их точки пересечения, структура struct slice { path before,after; } Глава 7. Модуль plain 83 Ïðèìåð 7.28. Èçâëå÷åíèå òî÷åê ïåðåñå÷åíèÿ èç äâóìåðíîãî ìàññèâà. size(6.5cm,0); path g=(-2,-1)..(0,1)..(2,-1)..(1,2).. (-1,2)..cycle; path s=(-2.5,2)..(0,0.5)..(2.5,2); path p=shift(0,-3.5)*g; path q=shift(0,-3.5)*s; draw(p,blue); draw(q,green); real[][] PQ=intersections(p,q); pair P=point(p,PQ[1][0]); dot(Label("P",black),P,1.5*SSW,red+3bp); pair Q=point(q,PQ[3][1]); dot(Label("Q",black),Q,2*dir(170),red+3bp); Q P и описанные в первой части документа функции путей: slice cut(path p, path knife, int n); slice firstcut(path p, path knife); slice lastcut(path p, path knife); В примере 7.29 путь-нож (knife) оранжевого цвета пересекает основной путь p четыре раза. В результате применения функции cut создается зеленый фрагмент пути p до третьей точки пересечения и синий фрагмент – после четвертой точки пересечения. Ïðèìåð 7.29. Âûäåëåíèå ôðàãìåíòîâ ïóòè. size(6.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); pair A=(-4,-1), B=(-2,1.5), C=(0,1.5), D=(2,0), E=(4,3.5); path p=(-4,.2){(1,0)}..{(1,2)}(4,3), knife=A{dir(0)}..B..(-1,-1.5)..C..D..{dir(0)}E; path bf=cut(p,knife,2).before, af=cut(p,knife,3).after; draw(p,mahogany); draw(knife,orange); draw(bf,2bp+green); draw(af,2bp+blue); Если из текста программы убрать оператор draw(p,mahogany);, то будут нарисованы лишь фрагменты пути p. В следующем примере 7.30 показано, как нарисовать фрагмент пути между двумя точками пересечения. 7.10 Ñîçäàíèå çàìêíóòûõ ïóòåé Чтобы создать замкнутый путь из кусочков уже имеющихся путей, в Asymptote имеется специальная функция buildcycle. В примере 7.31 незамкнутый путь зеленого цвета разбивает область, ограниченную замкнутым путем синего цвета, на две области. На рис. мы видим один из возможных замкнутых путей, красного цвета, построенный с помощью buildcycle. Чтобы понять механизм действия buildcycle, добавим к рисунку номера узлов, обозначив их тем же цветом, что и соответствующие им пути (рис. примера 7.32). Направление на кривых указывается возрастанием номеров узлов. Глава 7. Модуль plain 84 Ïðèìåð 7.30. Âûðåçàíèå ôðàãìåíòà ïóòè. size(6.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); pair A=(-4,-1), B=(-2,1.5), C=(0,1.5), D=(2,0), E=(4,3.5); path p=(-4,.2){(1,0)}..{(1,2)}(4,3), knife=A{dir(0)}..B..(-1,-1.5)..C..D..{dir(0)}E; path af1=cut(p,knife,2).after, af2=cut(p,knife,3).after; draw(af1,green); draw(af2,white+bp); draw(knife,orange); Ïðèìåð 7.31. Ïåðâûé âàðèàíò çàìêíóòîãî ïóòè. size(6.5cm,0); pair [] A; A[4]=(0,2); A[1]=(1,3); A[2]=(3,2); A[3]=(3,-1.5); pair [] B; B[1]=(-2,0); B[2]=(1,1.5); B[3]=(2,0); B[4]=(1,-2.5); path [] c; c[1] = A[1]..{(1,0)}A[2]..{(-1,0)}A[3]..A[4]..cycle; c[2] = B[1]..{(1,0)}B[2]..B[3]..B[4]; c[3] = buildcycle(c[1],c[2]); draw(c[1],green^^c[2],blue); draw(c[3], 2bp+red); Ïðèìåð 7.32. Ôîðìèðîâàíèå ïåðâîãî âàðèàíòà çàìêíóòîãî ïóòè. pair [] A; A[4]=(0,2); A[1]=(1,3); A[2]=(3,2); A[3]=(3,-1.5); pair [] B; B[1]=(-2,0); B[2]=(1,1.5); B[3]=(2,0); B[4]=(1,-2.5); path [] c; c[1] = A[1]..{(1,0)}A[2]..{(-1,0)}A[3]..A[4]..cycle; c[2] = B[1]..{(1,0)}B[2]..B[3]..B[4]; c[3] = buildcycle(c[1],c[2]); draw(c[1],blue); draw(c[2],green); draw(c[3], 1bp+red); for(int i=0; i<length(c[1]); ++i) dot(string(i),point(c[1],i),NW,blue); for(int i=0; i<=length(c[2]); ++i) dot(string(i),point(c[2],i),SW,green); for(int i=1; i<length(c[3]); ++i) dot(string(i),point(c[3],i),NW,red); dot(string(0),point(c[3],0),SSE,red); 0 3 1 1 2 1 3 2 0 0 2 3 В примере у функции buildcycle имеется два аргумента c[1] и c[2], причем, их порядок имеет значение. Движение пера начинается с нулевого узла, который определяется следующим образом. Поскольку первым аргументом buildcycle является c[1], то движение начинается из его Глава 7. Модуль plain 85 нулевой синей точки до первой точки его пересечения с зеленым путем c[2]. Эта точка пересечения (начальная точка формируемого замкнутого пути) обозначена на рис. нулем красного цвета. От нее перо движется вдоль синего пути до второй точки пересечения путей, которая обозначена красной единицей. Далее перо перемещается вдоль зеленого пути, проходя через узлы 1 и 2 зеленого цвета. Эти узлы становятся узлами нового замкнутого пути, почему и получают номера 2 и 3 красного цвета. Движение пера заканчивается в начальной точке с номером 0 (красного цвета). В следующем примере 7.33 вместо пути c[1] используется путь c[3], который отличается от первого только направлением обхода. Порядок аргументов buildcycle тоже другой: первым аргументом является c[2], а вторым – c[3]. Мы видим, что теперь получается совсем другой замкнутый путь красного цвета. Ïðèìåð 7.33. Âòîðîé âàðèàíò çàìêíóòîãî ïóòè. size(6.5cm,0); pair [] A; A[4]=(0,2); A[1]=(1,3); A[2]=(3,2); A[3]=(3,-1.5); pair [] B; B[1]=(-2,0); B[2]=(1,1.5); B[3]=(2,0); B[4]=(1,-2.5); path [] c; c[1] = A[1]..{(1,0)}A[2]..{(-1,0)}A[3]..A[4]..cycle; c[2] = B[1]..{(1,0)}B[2]..B[3]..B[4]; c[3] = reverse(c[1]); c[4] = buildcycle(c[2],c[3]); draw(c[3],blue); draw(c[2],green); draw(c[4],bp+red); Функция buildcycle может иметь более двух аргументов, как в примере 7.34, в котором эта функция находит новый замкнутый путь, используя части трех кривых. Ïðèìåð 7.34. Çàìêíóòûé ïóòü èç òðåõ êðèâûõ size(5cm,0); path [] c; c[1] = (-2,-.5){up}..tension 1.2..(2,-.5){down}; c[2] = rotate(60)*c[1]; c[3] = rotate(-60)*c[1]; c[4] = rotate(180)*c[1]; draw(c[2]^^c[3]^^c[4],blue); c[5] = buildcycle(c[2],c[3],c[4]); fill(c[5],lightyellow); draw(c[5],red+2bp); Пример 7.35 демонстрирует возможности использования замыкания путей для закрашивания областей плоскости. Вначале каждый круг закрашивается одним цветом: красным, зеленым и синим, затем с помощью процедуры buildcycle находятся замкнутые пути, возникающие при пересечении пар окружностей, и получаемые таким образом области закрашиваются цветами, равными сумме цветов, которыми были закрашены соответствующие круги. Затем отыскивается замкнутый путь, возникающий при пересечении всех трех окружностей, и закрашивается белым цветом. Глава 7. Модуль plain Ïðèìåð 7.35. Èñïîëüçîâàíèå çàìêíóòûõ ïóòåé èç äâóõ è òðåõ êðèâûõ. size(5cm,0); path a,b,c; a = shift(1,0)*scale(2)*unitcircle; b = rotate(120)*a; c = rotate(120)*b; fill(a, red); fill(b, green); fill(c, blue); fill(buildcycle(a,b), red + green); fill(buildcycle(b,c), green + blue); fill(buildcycle(c,a), blue + red); fill(buildcycle(a,b,c), white); draw(a^^b^^c); 86 Ãëàâà 8 Ìîäóëü graph Модуль graph реализует двумерные графики, включая линейное, логарифмическое и произвольное масштабирование, выбор отметок (с возможностью переопределения вручную) и присоединение списка обозначений. График представляет собой guide. 8.1 Îñè êîîðäèíàò 8.1.1 Ïðîöåäóðà xaxis Ось абсцисс создается процедурой void xaxis(picture pic=currentpicture, Label L="", axis axis=YZero, real xmin=-infinity, real xmax=infinity, pen p=currentpen, ticks ticks=NoTicks, arrowbar arrow=None, bool above=false); которая рисует ось Ox на картинке pic от абсциссы õ=Xmin до абсциссы õ=Xmax пером ð, снабжая ось, если требуется, меткой L. Относительное расположение метки вдоль оси регулируется вещественным числом из диапазона [0,1], которое по умолчанию равно 1, так что в этом случае метка располагается в конце оси. Бесконечное значение Xmin или Xmax означает, что ось будет автоматически ограничиваться теми ограничениями, которые накладываются на картинку в целом. Необязательный аргумент arrow принимает те же значения, что и в процедуре draw. Ось рисуется поверх любых имеющихся на рисунке объектов, если above=true. В примере 8.1 вначале ось абсцисс нарисована такой, какой она предполагается по умолчанию: черного цвета, без стрелки и название переменной расположено внизу. Вторая ось уже имеет стрелку. Наконец, третья координатная ось изображена разными цветами, а ее метка помещена справа от стрелки. Расположение оси определяется одной из следующих процедур. YZero(bool extend = true) Рисует ось, начиная с y = 0 (или y = 1 для логарифмической оси) до самого края картинки, если не установлено extend=false. YEquals(real Y, bool extend=true) Рисует ось, начиная с y = Y до края картинки, если не установлено extend=false. Bottom(bool extend=false) Ось будет внизу рисунка. Top(bool extend=false) Ось будет вверху рисунка. BottomTop(bool extend=false) Оси будут и внизу, и вверху рисунка. 87 Глава 8. Модуль graph 88 Ïðèìåð 8.1. Ïðîñòûå âàðèàíòû îñè àáñöèññ. import graph; size(6.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); picture pic1,pic2; xaxis("$x$"); xaxis(pic1,"$x$",arrow=Arrow); add(shift(0,-1)*pic1); xaxis(pic2,L=Label("$x$",align=2*right,mahogany), xmin=-3,xmax=4.5,p=orange, arrow=Arrow(2mm,Fill(mahogany))); add(shift(0,-2)*pic2); x x x Пользователь может построить требуемую ось, просто модифицируя стандартную процедуру вида import graph; YZero=new axis(bool extend=true) { return new void(picture pic, axisT axis) { real y=pic.scale.x.scale.logarithmic ? 1 : 0; axis.value=I*pic.scale.y.T(y); axis.position=1; axis.side=right; axis.align=2.5E; axis.value2=Infinity; axis.extend=extend; }; }; YZero=YZero(); По умолчанию черточки-отметки (ticks) на оси не изображаются, что́ выражается равенством ticks=NoTicks в определении процедуры xaxis. Применяя опции LeftTicks: ticks LeftTicks(Label format="", ticklabel ticklabel=null, bool beginlabel=true, bool endlabel=true, int N=0, int n=0, real Step=0, real step=0, bool begin=true, bool end=true, tickmodifier modify=None, real Size=0, real size=0, bool extend=false, pen pTick=nullpen, pen ptick=nullpen); RightTicks или Ticks, можно рисовать черточки слева, справа или с обеих сторон пути по направлению его прохождения. При отсутствии в процедуре рисования черточек каких-либо необязательных аргументов, по умолчанию будут использованы подходящие значения. Параметры имеют следующий смысл. Label format Перекрывает формат метки для черточки (defaultformat, "$%.4g$"), ее вращение, перо и выравнивание относительно оси (например, LeftSide, Center, RightSide). Чтобы обеспечить возможность использования математической моды LATEX, строка должна начинаться и заканчиваться символами $. Если строка содержит нули в младших разрядах, эти нули будут добавлены к меткам; вывод метки будет подавлен, если строка имеет вид "%". Глава 8. Модуль graph 89 ticklabel Функция вида string(real x) возвращает метку (format(format.s,x) по определению) для каждой крупной черточки, отвечающей значению x. bool beginlabel Определяет, рисовать ли первую черточку. bool endlabel Определяет, рисовать ли последнюю черточку. int N Задает число интервалов, при автоматическом масштабировании равномерно расположенных на оси и разделенных крупными черточками; для логарифмической оси это означает число десятков между черточками. int n Определяет число интервалов, разделенных мелкими черточками, на которые делится каждый интервал, ограниченный крупными черточками. real Step Задает расстояние между крупными черточками (если N=0). real step Залает расстояние между мелкими черточками (если n=0). bool begin Определяет, рисовать ли первую крупную черточку. bool end Определяет, рисовать ли последнюю крупную черточку. tickmodifier modify; Необязательная функция, которая принимает и возвращает структуру tickvalue типа real [], элементами которой являются major и minor, состоящие из значений черточек (чтобы разрешить модификацию автоматически генерируемых значений черточек). real Size Устанавливает длину крупных черточек (в PostScript-координатах). real size Устанавливает длину мелких черточек (в PostScript-координатах). bool extend Рисует черточки между двумя осями (полезно для изображения сетки). pen pTick Определяет перо, используемое для рисования крупных черточек. pen ptick Определяет перо, используемое для рисования мелких черточек. На рис. примера 8.2 оси координат снабжены отметками. Для первой оси использовалась опция ticks=LeftTicks, для второй – ticks=RightTicks, для третьей – ticks=Ticks. Первая и последняя длинные черточки не рисовались, короткие и длинные черточки изображались разными цветами. Для удаления части автоматически генерируемые черточек и их меток используют предопределенные модификации OmitTick (... real[] x) è OmitTickIntervals(real[] a, real[]b). Модификация OmitFormat(string s=defaultformat ... real[] x) может быть применена для удаления некоторых меток, но не соответствующих им черточек. Модификация NoZero является аббревиатурой для OmitTick(0), а модификация NoZeroFormat является сокращением для OmitFormat(0). Применение этих модификаций показано в примере 14.3. Некоторые черточки удалены вместе с их метками. Можно задать собственную локализацию черточек, используя LeftTicks, RightTicks и Ticks и заполняя массив Ticks и необязательный массив ticks, содержащие, соответственно, крупные и мелкие черточки: Глава 8. Модуль graph 90 Ïðèìåð 8.2. Ñòàíäàðòíûå îòìåòêè íà îñÿõ. import graph; size(7.5cm,0); picture pic1,pic2; xaxis("$x$",xmin=-3,xmax=3, ticks=LeftTicks(N=6,n=5,begin=false, end=false,pTick=blue,ptick=green)); xaxis(pic1,"$x$",xmin=-3,xmax=3, ticks=RightTicks(N=6,n=5,begin=false, end=false,pTick=blue,ptick=green)); add(shift(0,-1.5)*pic1); xaxis(pic2,"$x$",xmin=-3,xmax=3, ticks=Ticks(N=6,n=5,begin=false, end=false,pTick=blue,ptick=green)); add(shift(0,-3)*pic2); −3 −2 −1 0 1 2 3 x −3 −2 −1 0 1 2 3 x −3 −2 −1 0 1 2 3 x −1 0 Ïðèìåð 8.3. Óäàëåíèå íåêîòîðûõ îòìåòîê. import graph; size(7.5cm,0); xaxis(L="$x$",axis=YZero,xmin=-3,xmax=3, ticks=Ticks(ticklabel=OmitFormat(1), endlabel=false,Step=1,step=.5,end=false, modify=OmitTick(-2),pTick=bp+red, ptick=bp+.8green),arrow=Arrow(2mm)); −3 2 x ticks LeftTicks(Label format="", ticklabel ticklabel=null, bool beginlabel=true, bool endlabel=true, real[] Ticks, real[] ticks=new real[], real Size=0, real size=0, bool extend=false, pen pTick=nullpen, pen ptick=nullpen) В примере 8.4 как раз и изображаются только те черточки, координаты которых заданы специальными массивами. Ïðèìåð 8.4. Çàäàíèå îòìåòîê ìàññèâàìè. import graph; size(7.5cm,0); real[] tabT={-2,0,1}, tabt={-1.5,-0.5,0.5}; xaxis(L="$x$",axis=YZero(extend=true), xmin=-3,xmax=3,ticks=Ticks(Ticks=tabT, ticks=tabt,pTick=bp+red,ptick=green), arrow=Arrow(2mm),above=false); 8.1.2 Ïðîöåäóðà −2 0 1 yaxis Ось ординат изображается процедурой void yaxis(picture pic=currentpicture, Label L="", axis axis=XZero, real ymin=-infinity, real ymax=infinity, pen p=currentpen, x Глава 8. Модуль graph 91 ticks ticks=NoTicks, arrowbar arrow=None, bool above=false, bool autorotate=true); Смысл аргументов аналогичен смыслу аргументов процедуры xaxis. Отличие заключается в том, что имеется параметр autorotate, при значении которого true метка оси поворачивается. Расположение оси определяется с помощью одного из следующих процедур: XZero(bool extend=true) Рисует ось, начиная с x = 0 (или x = 1 для логарифмической оси), до самого края картинки, если не установлено extend=false. XEquals(real X, bool extend=true) Рисует ось, начиная с x = X, до края картинки, если не установлено extend=false. Left(bool extend=false) Ось будет расположена слева. Right(bool extend=false) Ось будет расположена справа. LeftRight(bool extend=false) Ось будет расположена и слева, и справа. На рис. примера 8.5 продемонстрировано использование двух осей ординат. Ïðèìåð 8.5. Äâå îñè îðäèíàò. import graph; size(7.5cm,0); xaxis(L=Label("$x$",align=4*right),xmin=-3, xmax=3,ticks=Ticks(endlabel=false, beginlabel=false,Step=1,step=.5, end=false,pTick=bp+red, ptick=bp+.8green),arrow=Arrow); yaxis(L="$y$", axis=LeftRight,ymin=-1, ymax=3,ticks=Ticks(beginlabel=false, begin=false,end=false,Step=1,step=.25, pTick=bp+red,ptick=bp+.8green), arrow=Arrow); 8.1.3 3 2 y 1 x 0 −2 −1 0 1 2 Ïðîöåäóðû xlimits, ylimits è limits Минимальное и максимальное значения x для графика можно зафиксировать с помощью процедуры xlimits(picture pic=currentpicture, real min=-infinity, real max=infinity, bool crop=NoCrop); и аналогичной процедуры ylimits для фиксации минимального и максимального значений y. Функция void limits(picture pic=currentpicture, pair min, pair max, bool crop=NoCrop); может быть использована, чтобы заключить координатные оси в прямоугольник, противоположными вершинами которого являются пары min и max. Созданные на картинке pic объекты будут обрезаны по заданным границам, если crop=Crop. Для обрезания графиков по заданным границам существует и отдельная функция crop(picture pic). В следующем примере 8.6 кое-что из рассмотренного применено для построения координатной сетки. Там же показано использование установки extend=true. Глава 8. Модуль graph 92 Ïðèìåð 8.6. Êîîðäèíàòíàÿ ñåòêà. import graph; size(7.5cm,0); 3 xlimits(-3, 3); ylimits(-1, 3); xaxis(L="$x$", axis=BottomTop, ticks=Ticks(Step=1,step=.5,extend=true, pTick=bp+red,ptick=1.2bp+dotted), arrow=Arrow); 2 y 0 yaxis(L="$y$", axis=LeftRight, ticks=Ticks(Step=1,step=.5,extend=true, pTick=bp+red,ptick=1.2bp+dotted), arrow=Arrow,above=true); 8.1.4 Ïðîöåäóðû xequals è 1 −1 −3 −2 −1 0 x 1 2 3 yequals Функции void xequals(picture pic=currentpicture, Label L="", real x, bool extend=false, real ymin=-infinity, real ymax=infinity, pen p=currentpen, ticks ticks=NoTicks, bool above=true, arrowbar arrow=None); void yequals(picture pic=currentpicture, Label L="", real y, bool extend=false, real xmin=-infinity, real xmax=infinity, pen p=currentpen, ticks ticks=NoTicks, bool above=true, arrowbar arrow=None); могут быть использованы для вызова процедур xaxis и yaxis с подходящими значениями XEquals(x,extend) и YEquals(y,extend). Их рекомендуется применять для рисования вертикальных линий и осей в требуемом (не стандартном) расположении. Пример 8.7 показывает построение осей координат для нестандартных диапазонов значений x и y, причем, минимальные значения для осей указаны в аргументах функций xequals и yequals. Ïðèìåð 8.7. Íåñòàíäàðòíûå àáñöèññû è îðäèíàòû. import graph; size(7.5cm,0); xlimits(17,23); ylimits(52,55); xequals(L="$y$",x=17,ticks=Ticks(Step=1, step=.5,end=false), arrow=Arrow(HookHead)); yequals(L="$x$",y=52,ticks=Ticks(Step=1, step=.5,end=false), arrow=Arrow(HookHead)); y 55 54 53 52 17 18 19 20 21 22 23 x Следующие процедуры полезны для расстановки черточек и их меток на осях вручную (если переменная Label задана как аргумент Label, то аргумент format будет использовать формат строки, опирающийся на расположение черточки): Глава 8. Модуль graph 93 void xtick(picture pic=currentpicture, Label L="", explicit pair z, pair dir=N, string format="", real size=Ticksize, pen p=currentpen); void xtick(picture pic=currentpicture, Label L="", real x, pair dir=N, string format="", real size=Ticksize, pen p = currentpen); void ytick(picture pic=currentpicture, Label L="", explicit pair z, pair dir=E, string format="", real size=Ticksize, pen p=currentpen); void ytick(picture pic=currentpicture, Label L="", real y, pair dir=E, string format="", real size=Ticksize, pen p=currentpen); void tick(picture pic=currentpicture, pair z, pair dir, real size=Ticksize, pen p=currentpen); void labelx(picture pic=currentpicture, Label L="", explicit pair z, align align=S, string format="", pen p=currentpen); void labelx(picture pic=currentpicture, Label L="", real x, align align=S, string format="", pen p=currentpen); void labelx(picture pic=currentpicture, Label L, string format="", explicit pen p=currentpen); void labely(picture pic=currentpicture, Label L="", explicit pair z, align align=W, string format="", pen p=currentpen); void labely(picture pic=currentpicture, Label L="", real y, align align=W, string format="", pen p=currentpen); void labely(picture pic=currentpicture, Label L, string format="", explicit pen p=currentpen); Пример 8.8 демонстрирует, как процедуры labelx и labely позволяют изобразить числа для отметок на оси так, чтобы их не пересекали линии сетки. Ïðèìåð 8.8. Íåñòàíäàðòíûå àáñöèññû è îðäèíàòû. import graph; size(7.5cm,0); xlimits(-1, 5); ylimits(-2, 2); xaxis(BottomTop, Ticks("%",extend=true, pTick=linewidth(.5pt),ptick=bp+dotted)); yaxis(LeftRight, Ticks("%",extend=true, pTick=linewidth(.5pt),ptick=bp+dotted)); xequals(Label("$y$",align=2.3N),0, p=linewidth(bp),Arrow(2mm)); yequals(Label("$x$",align=2.3E),0, p=linewidth(bp),Arrow(2mm)); labelx(Label("$2$",UnFill),1); labely(Label("$2$",UnFill),1); dot("$O$",(0,0),1.6SW); 8.1.5 Ïðîöåäóðà y 2 O 2 x axes Процедура void axes(picture pic=currentpicture, Label xlabel="", Label ylabel="", bool extend=true, pair min=(-infinity,-infinity), Глава 8. Модуль graph 94 pair max=(infinity,infinity), pen p=currentpen, arrowbar arrow=None, bool above=false); позволяет нарисовать сразу и ось абсцисс, и ось ординат, обе одинаковой формы со стрелками на концах. Оси изображаются поверх уже нарисованных объектов, только если задано above=true. В примере 8.9 эта процедура использована для изображения двух координатных систем. Ïðèìåð 8.9. Äâå ñèñòåìû êîîðäèíàò. import graph; size(7.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); xlimits(-1,6); ylimits(-1,3); xaxis(L="$x$",p=bp+blue,ticks=Ticks(NoZero, endlabel=false,beginlabel=false,Step=1, y step=.5,begin=false,end=false), Y 3 arrow=Arrow(TeXHead)); yaxis(L="$y$",p=bp+blue,ticks=Ticks(NoZero, 2 1 beginlabel=false,Step=1,step=.5, begin=false,end=false), arrow=Arrow(TeXHead)); 1 A labelx("$O$",0,2*SW,blue); picture pic; unitsize(pic,1cm); axes(pic,xlabel=Label("$X$",align=2.3E), O 1 2 3 ylabel=Label("$Y$",align=2.3N), min=(-4,-2),max=(3,2),p=dashed+mahogany, arrow=Arrow(HookHead)); labelx(pic,"$A$",(0,0),2*SW,mahogany); xtick(pic,"$1$",1,size=1mm,mahogany);xtick(pic,dir=S,1,size=1mm,red); ytick(pic,"$1$",1,size=1mm,mahogany);ytick(pic,dir=W,1,size=1mm,red); add(pic.fit(),(3,1)); 8.1.6 Ïðîöåäóðà X 1 4 5 x axis С помощью процедуры void axis(picture pic=currentpicture, Label L="", path g, pen p=currentpen, ticks ticks, ticklocate locate, arrowbar arrow=None, int[] divisor=new int[], bool above=false, bool opposite=false); любой путь можно представить в виде оси. Путь можно снабдить меткой, стрелкой и разметить черточками. Опциональный параметр divisor вводится для того, чтобы откорректировать ситуацию, когда черточки мешают друг другу. Значение флага opposite=true сигнализирует о наличии дополнительной оси без метки. Ось рисуется поверх уже нарисованных объектов только при above=true. Процедура размещения черточек ticklocate имеет следующий вид: ticklocate ticklocate(real a, real b, autoscaleT S=defaultS, real tickmin=-infinity, real tickmax=infinity, real time(real)=null, pair dir(real)=zero); Глава 8. Модуль graph 95 где a и b – значения черточек в точках point(g,0) и point(g,length(g)), соответственно; S – автомасштабирование; функция time(real v) возвращает время, соответствующее значению v; пара dir(real t) – абсолютное направление черточки как функции t (нуль означает рисование черточки перпендикулярно оси). Некоторое представление о новом инструменте дает размеченный черточками эллипс, изображенный в примере 8.10. Ïðèìåð 8.10. Ðàçìå÷åííûé ýëëèïñ. import graph; size(7.5cm,0); path g=ellipse((0,0),2,1); axis(Label("C",align=10W,black),g,blue, LeftTicks(endlabel=false,8,end=false), ticklocate(0,360,new real(real v) {path h=(0,0)--max(abs(max(g)), abs(min(g)))*dir(v); return intersect(g,h)[0];})); 135 C 90 45 180 0 225 270 315 8.2 Ïîñòðîåíèå ãðàôèêîâ 8.2.1 Ñïëàéíû При построении кривых, в частности, графиков функций, Asymptote широко применяет сплайны. Для стандартной кубической интерполяции Эрмита используются такие граничные условия как notaknot, natural, periodic, clamped(real slopea, real slopeb) или monotonic. Условие notaknot означает применение правила not-a-knot, смысл которого состоит в том, чтобы не изменять кубические многочлены, когда они проходят через предконцевые узлы x1 и xn−1 (узлы образуют последовательность x0 , x1 , . . . , xn ). Математически это выражается формулой 000 000 p000 p000 1 (x1 ) = p2 (x1 ), n−1 (xn−1 ) = pn (xn−1 ), где pi – кубический многочлен сплайна для отрезка [xi−1 , xi ], i = 1, n. Применение этого правила может улучшить гладкость сплайна на концах отрезка интерполяции. Условие natural приводит к построению натурального кубического сплайна, который характеризуется выполнением ограничений p001 (x0 ) = 0, p00n (xn ) = 0. Периодическая функция аппроксимируется периодическим сплайном, построение которого инициирует параметр periodic. В этом случае первый и последний узлы интерполяции отождествляются: S(x0 ) = S(xn ), S 0 (x0 ) = S 0 (xn ), S 00 (x0 ) = S 00 (xn ), где S(x) – сплайн на отрезке [x0 , xn ]. Условие clamped(real slopea, real slopeb) задает «наклоны» на концах отрезка интерполяции (slopea и slopeb): p01 (x0 ) = f 0 (x0 ), p0n (xn ) = f 0 (x0 ), Глава 8. Модуль graph 96 где f (x) – интерполируемая функция. Условие monotonic гарантирует построение монотонного кубического сплайна. Условие Hermite равносильно Hermite(notaknot) для непериодических данных и эквивалентно Hermite(periodic) для периодических данных. 8.2.2 Äåêàðòîâà ñèñòåìà êîîðäèíàò Ïðîñòûå ãðàôèêè Графики функций формирует процедура graph вида guide graph(picture pic=currentpicture, real f(real), real a, real b, int n=ngraph, real T(real)=identity, interpolate join=operator --); guide[] graph(picture pic=currentpicture, real f(real), real a, real b, int n=ngraph, real T(real)=identity, bool3 cond(real), interpolate join=operator --); Результатом ее применения является график функции f, который с учетом масштабирования строится, вообще говоря, на отрезке [T(a),T(b)]. Функция T задает масштаб, например, логарифмический; по-другому это называется переходом к логарифмической шкале. Число n определяет количество точек, равномерно распределенных на отрезке [a,b] и используемых для построения графика; увеличение их количества может улучшить его качество. Необязательный параметр cond подчиняет построение графика некоторым условиям. Если cond равен • true, то точка добавляется к существующему пути guide; • default, то точка добавляется к новому пути guide; • false, то точка пропускается и начинается новый путь guide. Точки соединяются посредством интерполяционных кривых: • operator -- означает линейную интерполяцию, которая может быть вызвана и словом Straight; • operator .. задает интерполяцию кубическими сплайнами Безье; допустимо задание и словом Spline; • Hermite влечет стандартную интерполяцию кубическими сплайнами с применением граничных условий notaknot, natural, periodic, clamped(real slopea, real slopeb) или monotonic. Стандартный график функции формируется и выглядит, как в примере 8.11. Более «оснащенные» оси координат (снабженные метками и соответствующими им значениями) можно увидеть на рис. примера 10.6. Çàêðàñêà îáëàñòåé ìåæäó ãðàôèêàìè Представляя графики функций в виде путей, нетрудно получить закрашиваемые области под, над или между графиками. Так, в примере 8.12 части графиков функций представлены путями graph(f1,-0.5,0.5) и graph(f2,0.5,-0.5), которые затем преобразованы в циклический путь graph(f1,-0.5,0.5)-- graph(f2,0.5,-0.5)--cycle, внутренняя область которого далее закрашивается в светло-зеленый цвет. Глава 8. Модуль graph 97 2 Ïðèìåð 8.11. Ãðàôèê ôóíêöèè y = e−x cos 5x. import graph; size(7.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); real f(real x){ return exp(-x^2)*cos(5*x);} pair F(real x){ return (x,f(x));} draw(graph(f,-1.8,1.8,operator ..), blue); label("$O$",(0,0),SW,mahogany); axes(xlabel=Label("$x$",align=2E, mahogany),ylabel=Label("$y$",align=2N, mahogany),min=(-2,-0.8),max=(2,1.3), p=orange,arrow=Arrow(2mm,Fill(mahogany))); y x O Ïðèìåð 8.12. Çàêðàøèâàíèå îáëàñòè ìåæäó ãðàôèêàìè ôóíêöèé. import graph; size(7.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); pair f1(real x){return (x,1.2+0.5x^2*cos(5*x));} pair f2(real x){return (x,0.4+0.5*x^2*sin(5*x));} fill(graph(f1,-0.5,0.5)-graph(f2,0.5,-0.5)--cycle,palegreen); draw(graph(f1,-1,0.8),blue); draw(graph(f2,-1,0.8),blue); axes(xlabel=Label("$x$",align=2E,mahogany), ylabel=Label("$y$",align=2N,mahogany), min=(-1.1,0),max=(1,1.5),p=orange, arrow=Arrow(2mm,Fill(mahogany)), above=true); label("$O$",(0,0),S,mahogany); y O x Ïîñòðîåíèå ãðàôèêîâ ïî òî÷êàì Имеется возможность строить графики функций по массивам значений аргумента и функции. Для этого имеются специальные операторы. guide graph(picture pic=currentpicture, pair[] z, interpolate join=operator --); guide[] graph(picture pic=currentpicture, pair[] z, bool3[] cond, interpolate join=operator --); Для картинки pic строится график функции с учетом масштабирования, используя данные массива z; при этом выполняются ограничения на индексы массива, задаваемые условием cond, и используется тип интерполяции join. Вместо массива z можно задавать отдельные массивы x и y: guide graph(picture pic=currentpicture, real[] x, real[] y, interpolate join=operator --); Глава 8. Модуль graph 98 guide[] graph(picture pic=currentpicture, real[] x, real[] y, bool3[] cond, interpolate join=operator --); В примере 8.13 график функции синего цвет строится на основе массива чисел x и массива y, данные для которого рассчитываются по формуле y = x2 . Для красного графика оба массива xx и yy определяются числовыми данными. В первом случае использовалась линейная интерполяция, во втором – сглаживание .. . Для оси ординат был выбран уменьшенный размер шрифта. Ïðèìåð 8.13. Ïîñòðîåíèå ãðàôèêîâ ïî òî÷êàì. import graph; size(7.5cm,5cm,IgnoreAspect); real[] x={0,1,2,3}; real[] y=x^2; real[] xx={0,1,2,3}; real[] yy={9,8,5,0}; draw(graph(x,y),blue); draw(graph(xx,yy,operator..),red); xaxis("$x$",BottomTop,LeftTicks); yaxis("$y$",LeftRight, RightTicks(Label(fontsize(8pt)),new real[]{0,4,9})); 9 y 4 0 0 1 x 2 3 Ñïèñîê îáîçíà÷åíèé К картинке pic может быть добавлен окруженный рамкой список обозначений legend, который формирует процедура frame legend(picture pic=currentpicture, int perline=1, real xmargin=legendmargin, real ymargin=xmargin, real linelength=legendlinelength, real hskip=legendhskip, real vskip=legendvskip, real maxwidth=0, real maxheight=0, bool hstretch=false, bool vstretch=false, pen p=currentpen); Опции xmargin и ymargin определяют поля для x и y, соответственно. Параметр perline задает число элементов списка в строке (по определению равно 1; для 0 выбор этого числа производится автоматически). Длину черточек определяет параметр linelength. Опции hskip и vskip назначают пропуск строк (для многострочных элементов списка). Параметры maxwidth и maxheight определяют верхние границы для ширины и высоты списка (неограниченность фиксируется значением 0). Параметры hstretch и vstretch разрешают списку простираться по горизонтали или по вертикали. Рамка списка рисуется пером p. Список добавляется к картинке операторами add или attach. Рис. примера 8.14 изображает графики нескольких синусоид и снабжен списком их обозначений. Занесение элемента в список в соответствии со своим определением выполняет оператор draw: при каждом исполнении оператора в список в цикле заносится строка str с необходимой информацией. Присоединяет список к рисунку оператор attach. В следующем примере 8.15 список обозначений помещен внизу рисунка и имеет вид таблицы из двух строк и двух столбцов. Глава 8. Модуль graph 99 Ïðèìåð 8.14. Ãðàôèê ôóíêöèè ñî ñïèñêîì îáîçíà÷åíèé. import graph; size(9cm,0); pen[] pens={green,blue,red, cyan,orange,Magenta,purple}; typedef real realfcn(real); realfcn F(real m){ return new real(real x) {return (1+m)*sin(x);};}; 2.5 for (int i=0; i<=6; ++i) {real m=0.5*i; string str="$" + string(m+1) + "\sin x$"; y 0 i==0 ? str="$\sin x$" : str; draw(graph(F(m),-2pi,2pi), −2.5 pens[i],str);} xlimits(-2pi,2pi); ylimits(-4,4); −6 −4 xaxis("$x$",BottomTop, Ticks(Size=3bp,size=2bp)); yaxis("$y$",LeftRight,Ticks(Size=3bp,size=2bp)); draw((-2pi,0)--(2pi,0),black+dotted+bp); draw((0,-4)--(0,4),black+dotted+bp); attach(legend(linelength=0.3cm,p=blue), point(E),20E,UnFill); sin x 1.5 sin x 2 sin x 2.5 sin x 3 sin x 3.5 sin x 4 sin x −2 0 x 2 4 6 Ïðèìåð 8.15. Ìíîãîñòðî÷íûé ñïèñîê îáîçíà÷åíèé. import graph; size(7.5cm,5cm,IgnoreAspect); real exp1(real x){return exp(-x^2);} real exp2(real x){return -exp(-x^2);} real expcos(real x){return exp(-x^2)*cos(5*x);} real expsin(real x){return exp(-x^2)*sin(5*x);} draw(graph(exp1,-2,2),green,"$e^{-x^2}$"); draw(graph(exp2,-2,2),Cyan,"$-e^{-x^2}$"); draw(graph(expsin,-2,2),red, "$e^{-x^2}\sin 5x$"); draw(graph(expcos,-2,2),blue, "$e^{-x^2}\cos 5x$"); xlimits(-2,2); ylimits(-1,1); xaxis("$x$",BottomTop,Ticks(Size=3bp, size=2bp)); yaxis("$y$",LeftRight,Ticks(Size=3bp, size=2bp)); attach(legend(2,linelength=0.3cm), (point(S).x,truepoint(S).y),10S,UnFill); 1 0.5 y 0 −0.5 −1 −2 −1 2 e−x 2 e−x sin 5x 0 x 1 −e−x 2 2 2 e−x cos 5x Ìàðêåðû Графики функций могут быть помечены маркерами. Маркер создается функцией Глава 8. Модуль graph 100 marker marker(path g, markroutine markroutine=marknodes, pen p=currentpen, filltype filltype=NoFill, bool above=true); Любой фрейм может быть преобразован в маркер с помощью процедуры marker marker(frame f, markroutine markroutine=marknodes, bool above=true); Процедура marknodes обеспечивает маркирование узлов Безье на кривой. Можно использовать и процедуру markuniform(pair z(real t), real a, real b, int n), чтобы разместить маркеры в точках z(t), отвечающих n значениям t, равномерно расположенным на отрезке [a,b]. Вот предопределенные маркеры: marker[] Mark={ marker(scale(circlescale)*unitcircle), marker(polygon(3)), marker(polygon(4)), marker(polygon(5)), marker(invert*polygon(3)), marker(cross(4)), marker(cross(6))}; marker[] MarkFill={ marker(scale(circlescale)*unitcircle,Fill), marker(polygon(3),Fill), marker(polygon(4),Fill), marker(polygon(5),Fill), marker(invert*polygon(3),Fill)}; Для обозначения границ погрешности могут быть использованы специальные процедуры: void errorbars(picture pic=currentpicture, pair[] z, pair[] dp, pair[] dm={}, bool[] cond={}, pen p=currentpen, real size=0); void errorbars(picture pic=currentpicture, real[] x, real[] y, real[] dpx, real[] dpy, real[] dmx={}, real[] dmy={}, bool[] cond={}, pen p=currentpen, real size=0); Положительные и отрицательные границы погрешности указываются элементами массива dp и необязательного массива dm. Если массив dm не определен, положительные и отрицательные границы погрешности предполагаются одинаковыми. Пример 8.16 демонстрирует как создание маркера, так и его применение. На графике показаны также границы погрешностей в узлах графика. В следующем примере 8.17 конструируется пользовательский фрейм маркера из правильного шестиугольника (процедура polygon(int n), см. выше) и циклической звездочки (процедура cross(int n, bool round=true, real r=0), где n – число сторон многоугольника, r – необязательный «внутренний» радиус). Копии этого фрейма добавляет к рисунку процедура markuniform(bool centered=false, int n, bool rotated=false), располагая их в пяти точках, равномерно распределенных вдоль графика. При этом по умолчанию осуществляется поворот на угол, соответствующий углу наклона касательной в данной точке графика Глава 8. Модуль graph 101 Ïðèìåð 8.16. Ìàðêèðîâêà ãðàôèêà ñ óêàçàíèåì ïîãðåøíîñòè. import graph; 100 size(7.5cm,5cm,IgnoreAspect); pair[] f={(5,5),(50,20),(90,90)}; pair[] df={(0,0),(5,7),(0,5)}; errorbars(f,df,red); draw(graph(f),"legend", marker(scale(0.8mm)*unitcircle,red, FillDraw(blue),above=false)); xaxis("$x$",BottomTop,LeftTicks); yaxis("$y$",LeftRight,RightTicks); attach(legend(linelength=0.5cm),point(W), 20ENE,UnFill); 80 y legend 60 40 20 0 0 20 40 x 60 80 100 Ïðèìåð 8.17. Ìàðêèðîâêà ãðàôèêà. import graph; size(7.5cm,5cm,IgnoreAspect); pair[] f={(5,5),(50,20),(90,90)}; frame mark; filldraw(mark,scale(0.8mm)*polygon(6), green,green); draw(mark,scale(0.8mm)*cross(6),blue); draw(graph(f),marker(mark,markuniform(5))); xaxis("$x$",BottomTop,LeftTicks); yaxis("$y$",LeftRight,RightTicks); yequals(55.0,red+Dotted); xequals(70.0,red+Dotted); 80 60 y 40 20 10 20 30 40 50 60 70 80 90 x (если требуется центрирование, маркеры будут отцентрированы внутри n равномерно распределенных вдоль кривой интервалов). Маркеры можно создавать и программно, фактически для каждого узла графика конструируя собственный маркер. Эту возможность демонстрирует пример 8.18. В следующем примере 8.19 осуществляется стандартная маркировка точками, а горизонтальная ось помечается строками, являющимися названиями месяцев. Ìàñøòàáèðîâàíèå Масштабирование по координатным осям может быть выполнено вручную, как это показано в примере 8.20. Автоматическое масштабирование осей может быть осуществлено с помощью одной из следующих процедур: void scale(picture pic=currentpicture, scaleT x, scaleT y); void scale(picture pic=currentpicture, bool xautoscale=true, bool yautoscale=xautoscale, bool zautoscale=yautoscale); Эти процедуры задают масштабирование для картинки pic. Процедуры модуля graph применяют масштабирование к необязательноу аргументу picture; если он не задан, масштабирвание применяется к currentpicture. Глава 8. Модуль graph 102 Ïðèìåð 8.18. Ïðîãðàììèðóåìàÿ ìàðêèðîâêà. import graph; size(7.5cm,5cm,IgnoreAspect); markroutine marks() { return new void(picture pic=currentpicture,frame f,path g){ path p=scale(1mm)*unitcircle; for(int i=0; i <= length(g); ++i) { pair z=point(g,i); frame f; if(i % 4 == 0) { fill(f,p); add(pic,f,z); } else { if(z.y > 50) { pic.add(new void(frame F, transform t) { path q=shift(t*z)*p; unfill(F,q); draw(F,q); }); } else { draw(f,p); add(pic,f,z); }}}}; } pair[] f={(5,5),(40,20),(55,51),(90,30)}; draw(graph(f),marker(marks())); scale(true); xaxis("$x$",BottomTop,LeftTicks); yaxis("$y$",LeftRight,RightTicks); 60 50 40 y 30 20 10 0 0 20 40 x 60 80 100 Ïðèìåð 8.19. Ñòàíäàðòíàÿ ìàðêèðîâêà. import graph; size(7.5cm,5cm,IgnoreAspect); real[] x=sequence(12); real[] y=sin(2pi*x/12); scale(false); string[] month={"Jan","Feb","Mar", "Apr","May","Jun","Jul","Aug","Sep", "Oct","Nov","Dec"}; draw(graph(x,y),red,MarkFill[0]); xaxis(BottomTop,LeftTicks(new string(real x){ return month[round(x % 12)];})); yaxis("$y$",LeftRight,RightTicks(4)); 1 0.5 y 0 −0.5 −1 Jan Apr Jul Oct В модуле graph предопределены две часто используемые процедуры Linear и Log. Все координаты картинки, включая те, которые задают пути и положение меток, трактуются как линейные. Чтобы преобразовать координаты графика в масштабированные координаты картинки, следует использовать pair Scale(picture pic=currentpicture, pair z); Применяя аналогичные процедуры, можно отдельно масштабировать как x, так и y: real ScaleX(picture pic=currentpicture, real x); Глава 8. Модуль graph 103 Ïðèìåð 8.20. Ïðîñòîå ìàñøòàáèðîâàíèå. 10 8 y/105 import graph; size(7.5cm,5cm,IgnoreAspect); real[] x={-1e-11,1e-11}; real[] y={0,1e6}; real xscale=round(log10(max(x))); real yscale=round(log10(max(y)))-1; draw(graph(x*10^(-xscale),y*10^(-yscale)), red); xaxis("$x/10^{"+(string) xscale+"}$",BottomTop,LeftTicks); yaxis("$y/10^{"+(string) yscale+"}$",LeftRight, RightTicks(trailingzero)); 6 4 2 0 −1 −0.5 0 x/10−11 0.5 1 real ScaleY(picture pic=currentpicture, real y); Для предопределенных процедур масштабирования могут быть заданы два необязательных булевских арумента automin=false и automax=automin. Оба они установлены в false, но могут быть переустановлены в true, чтобы состоялся автоматический выбор «хороших» значений, минимального и максимального. Процедура масштабирования Linear может также иметь в качестве аргумента мультипликативный масштабирующий множитель, а Linear(-1) означает обращение оси. В примере 8.21 показано, как получить график функции, осуществив его масштабирование вида log/log. Ïðèìåð 8.21. Ëîãàðèôìè÷åñêîå ìàñøòàáèðîâàíèå. import graph; 101 size(7.5cm,5cm,IgnoreAspect); real f(real t) {return 1/t;} scale(Log,Log); draw(graph(f,0.1,10)); dot(Label("(3,5)",align=S),Scale((3,5))); xaxis("$x$",BottomTop,LeftTicks); yaxis("$y$",LeftRight,RightTicks); (3,5) y 100 10−1 −1 10 100 x 101 Используя черточки-отметки на осях, нетрудно получить для системы координат логарифмическую сетку, как это показано на рис. примера 8.22. Для логарифмически масштабированных осей можно задать требуемую локализацию и формат черточек-отметок, см. пример 8.23. Не составляет труда использовать логарифмическое масштабирование с другим основанием логарифмов. В примере 8.24 ось ординат масштабируется двоичными логарифмами. При масштабировании иногда возникает необходимость сделать для наглядности разрыв в изображении графика функции. В примере 8.25 из оси абсцисс, имеющей линейное масштабирование, исключается отрезок [3; 8], а из оси ординат с логарифмическим масштабированием исключается отрезок [100; 1000]. Для последней оси границы разрыва автоматически округляются до ближайшей целой степени основания логарифма. Глава 8. Модуль graph 104 Ïðèìåð 8.22. Ëîãàðèôìè÷åñêàÿ êîîðäèíàòíàÿ ñåòêà. import graph; 101 size(7.5cm,5cm,IgnoreAspect); real f(real t) {return 1/t;} y 100 scale(Log,Log); draw(graph(f,0.1,10),red); pen thin=linewidth(0.5*linewidth()); xaxis("$x$",BottomTop,LeftTicks(begin=false, end=false,extend=true,ptick=thin)); 10−1 −1 10 yaxis("$y$",LeftRight,RightTicks(begin=false, end=false,extend=true,ptick=thin)); 100 x 101 Ïðèìåð 8.23. Îòìåòêè ïðè ëîãàðèôìè÷åñêîì ìàñøòàáèðîâàíèè. 100 νupp [Hz] import graph; size(7.5cm,5cm,IgnoreAspect); scale(Log,Log); draw(graph(identity,5,20)); xlimits(5,20); ylimits(1,100); xaxis("$M/M_\odot$",BottomTop, LeftTicks(DefaultFormat,new real[] {6,10,12,14,16,18})); yaxis("$\nu_{\rm upp}$ [Hz]",LeftRight,RightTicks (DefaultFormat)); 10 1 6 10 12 14 16 18 M/M⊙ Ïðèìåð 8.24. Ëîãàðèôìè÷åñêîå ìàñøòàáèðîâàíèå îñè Oy ïî îñíîâàíèþ 2. import graph; size(7.5cm,5cm,IgnoreAspect); real log2(real x) {static real log2=log(2); return log(x)/log2;} real pow2(real x) {return 2^x;} scaleT yscale=scaleT(log2,pow2, logarithmic=true); scale(Linear,yscale); real f(real x) {return 1+x^2;} draw(graph(f,-4,4),red); yaxis("$y$",ymin=1,ymax=f(5),RightTicks (Label(Fill(white))),EndArrow); xaxis("$x$",xmin=-5,xmax=5,LeftTicks, EndArrow); y 24 23 22 21 −5 20 0 5 x Имеется возможность дублировать координатную ось осью с другим масштабированием. Для этого служат процедуры picture secondaryX(picture primary=currentpicture, void f(picture)); picture secondaryY(picture primary=currentpicture, void f(picture)); В примере 8.26 вторая ось ординат secondaryY используется с линейным масштабом в про- Глава 8. Модуль graph 105 Ïðèìåð 8.25. Ëîãàðèôìè÷åñêîå ìàñøòàáèðîâàíèå ñ ðàçðûâîì ãðàôèêà. ≈ 104 ≈ y ≈ 101 100 ≈ import graph; size(7.5cm,5cm,IgnoreAspect); real a=3, b=8; real c=100, d=1000; scale(Broken(a,b),BrokenLog(c,d)); real[] x={1,2,4,6,10}; real[] y=x^4; draw(graph(x,y),red,MarkFill[0]); xaxis("$x$",BottomTop, LeftTicks(Break(a,b))); yaxis("$y$",LeftRight, RightTicks(Break(c,d))); label(rotate(90)*Break,(a,point(S).y)); label(rotate(90)*Break,(a,point(N).y)); label(Break,(point(W).x,ScaleY(c))); label(Break,(point(E).x,ScaleY(c))); 2 10 x тивоположность исходной оси с логарифмическим масштабированием. 8.2.3 102 1.5 101 1.0 100 0.5 10−1 (1,0) 10−2 10−3 −2 10 0.0 −0.5 ωτ0 100 −1.0 Ïàðàìåòðè÷åñêîå çàäàíèå ôóíêöèè Чтобы сформировать график однопараметрической функции, применяют операторы guide graph(picture pic=currentpicture, real x(real), real y(real), real a, real b, int n=ngraph, real T(real)=identity, interpolate join=operator --); Arg G/π import graph; size(7.5cm,7cm,IgnoreAspect); texpreamble("\def\Arg{\mathop {\rm Arg}\nolimits}"); real ampl(real x) {return 2.5/(1+x^2);} real phas(real x) {return -atan(x)/pi;} scale(Log,Log); draw(graph(ampl,0.01,10)); ylimits(0.001,100); xaxis("$\omega\tau_0$",BottomTop,LeftTicks); yaxis("$|G(\omega\tau_0)|$",Left,RightTicks); picture q=secondaryY(new void(picture pic) { scale(pic,Log,Linear); draw(pic,graph(pic,phas,0.01,10),red); ylimits(pic,-1.0,1.5); yaxis(pic,"$\Arg G/\pi$",Right,red, LeftTicks("$% #.1f$", begin=false,end=false)); yequals(pic,1,Dotted);}); label(q,"(1,0)",Scale(q,(1,0)),red); add(q); |G(ωτ0)| Ïðèìåð 8.26. Äâå îñè îðäèíàò ñ ðàçëè÷íûì ìàñøòàáèðîâàíèåì. Глава 8. Модуль graph 106 guide[] graph(picture pic=currentpicture, real x(real), real y(real), real a, real b, int n=ngraph, real T(real)=identity, bool3 cond(real), interpolate join=operator --); Процедура возвращает путь в виде графика параметрически заданной функции (x(t),y(t)) для t из отрезка [T(a),T(b)], который подвергается дискретизации с помощью n точек, равномерно распределенных на отрезке [a,b]. Используется масштабирование, определенное для картинки pic. Необязательная функция cond типа bool3 позволяет вводить ограничения, а join задает вид интерполяции. Той же цели служат и следующие две процедуры, в которых вместо отдельных функций x(t) и y(t) используется функция-пара z(t): guide graph(picture pic=currentpicture, pair z(real), real a, real b, int n=ngraph, real T(real)=identity, interpolate join=operator --); guide[] graph(picture pic=currentpicture, pair z(real), real a, real b, int n=ngraph, real T(real)=identity, bool3 cond(real), interpolate join=operator --); Пример 8.27 демонстрирует практическую реализацию возможностей Asymptote при изображении параметрически заданных функций. Фактически разрисовывается один и тот же лепесток «цветка» (но разными красками), который затем с помощью оператора поворота приводится в требуемое положение на рис. Следует обратить внимание на довольно большое значение параметра n=3500 в процедуре draw, так как именно этим достигается ажурность рис. Ïðèìåð 8.27. Ïàðàìåòðè÷åñêè çàäàííàÿ ôóíêöèÿ ¾Öâåòèê-ñåìèöâåòèê¿. import graph; size(7.5cm,0); real r1=100, r2=25, a1=1, a2=500; real x(real t){ return r1*(1+cos(7*t))*cos(a1*t)+r2*cos(a2*t);} real y(real t){ return r1*(1+cos(7*t))*sin(a1*t)+r2*sin(a2*t);} fill(box((-225,-235),(240,235)),black); real tt=1*0.449, ang=51.429; void mydraw(int n,pen col){ draw(rotate(n*ang)*graph(x,y,-tt,tt,n=3500, join=operator..),col);} mydraw(0,orange+white); mydraw(1,magenta); mydraw(2,lightblue); mydraw(3,cyan); mydraw(4,red); mydraw(5,yellow); mydraw(6,green); 8.2.4 Ïîëÿðíàÿ ñèñòåìà êîîðäèíàò Графики функций в полярной системе координат формирует оператор guide polargraph(picture pic=currentpicture, real f(real), real a, real b, int n=ngraph, interpolate join=operator --); Глава 8. Модуль graph 107 Процедура возвращает график функции f, заданной на отрезке [a,b], в полярной системе координат. Указанный отрезок дискретизуется с помощью n точек, равномерно распределенных на отрезке. Используется масштабирование, определенное для картинки pic. В примере 8.28 показано применение этой процедуры к построению графика функции с заполнением и отрисовкой контура. Значение параметра n=7000 здесь в два раза больше, чем в примере 8.27, но и его маловато: контур листа изрезан недостаточно. Ïðèìåð 8.28. Ãðàôèê ôóíêöèè â ïîëÿðíîé ñèñòåìå êîîðäèíàò. import graph; size(7.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); real rho(real t){ return (1+sin(9*t))*(1+sin(t))* (1+0.33*sin(9*5*t))* (1+0.04*sin(9*33*t)); } guide g=polargraph(rho,0,2pi,n=7000, join=operator..); filldraw(g--cycle,deepgreen,orange); Еще одна процедура конструирует графики в полярной системе координат, извлекая информацию о функции из массивов (r,theta). guide polargraph(picture pic=currentpicture, real[] r, real[] theta, interpolate join=operator--); 8.2.5 Èçîáðàæåíèå âåêòîðíûõ ïîëåé Чтобы нарисовать векторное поле в виде стрелок, равномерно распределенных вдоль пути, используется процедура picture vectorfield(path vector(real), path g, int n, bool truesize=false, pen p=currentpen, arrowbar arrow=Arrow); Такое векторное поле показано на рис. примера 8.29. Следует обратить внимание на то, что vectorfield представляет собой картинку picture, поэтому добавляется к текущему рисунку с помощью процедуры add. Чтобы нарисовать векторное поле с nx×ny стрелками в прямоугольнике box(a,b), следует использовать процедуру picture vectorfield(path vector(pair), pair a, pair b, int nx=nmesh, int ny=nx, bool truesize=false, real maxlength=truesize ? 0 : maxlength(a,b,nx,ny), bool cond(pair z)=null, pen p=currentpen, arrowbar arrow=Arrow, margin margin=PenMargin); Глава 8. Модуль graph 108 Ïðèìåð 8.29. Âåêòîðíîå ïîëå âäîëü êðèâîé. import graph; size(7.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); real f(real x) {return sin(x);} path g=graph(f,0,2pi); draw(g,blue); typedef path vector(real); y real arrowlength=3.5mm; vector vector(pair a, pair b) { return new path(real x) { return O (0,0)--arrowlength*interp(a,b,x);};} add(vectorfield(vector(N,N),g,30,true,red, arrow=Arrow(1mm))); axes(xlabel=Label("$x$",align=2E,mahogany), ylabel=Label("$y$",align=2N,mahogany),min=(0,-1.2), max=(2pi+0.5,1.3),p=orange,arrow=Arrow(2mm,Fill(mahogany))); label("$O$",(0,0),W,mahogany); x Пример 8.30 демонстрирует реализацию такой возможности. Можно отметить рекордную лаконичность программы, ядро которой состоит из одной строчки кода. Ïðèìåð 8.30. Âåêòîðíîå ïîëå â ïðÿìîóãîëüíèêå. import graph; size(6cm,0); pair a=(0,0); pair b=(2pi,2pi); path vector(pair z) {return (0,0)--(sin(z.x),cos(z.y));} add(vectorfield(vector,a,b,blue)); Ãëàâà 9 Ìîäóëü palette Средства модуля служат для раскрашивания геометрических объектов с помощью различных палитр, в том числе предопределенных. Первоначальные сведения о палитрах были приведены в п. 6.2.1. Функция cmyk(pen[] Palette) используется для преобразования предопределенных и определенных пользователем палитр в цветовое пространство CMYK. График типа density plot, использующий палитру palette, можно сформировать с помощью функции f(x, y) и добавить к картинке pic: bounds image(picture pic=currentpicture, real f(real, real) range range=Full, pair initial, pair final, int nx=ngraph, int ny=nx, pen[] palette, bool antialias=false); Функция f дискретизуется в прямоугольнике, определяемом точками initial и final, с помощью nx×ny точек, равномерно распределенных в прямоугольнике. При этом учитывается текущее масштабирование pic. Цветовое пространство шкалируется в соответствии со шкалированием оси Oz. Результатом работы процедуры является следующая структура: struct bounds { real min; real max; // Âîçìîæíûå èíòåðâàëû îòìåòîê ticks int[] divisor; } Эта информация может быть использована для создания необязательной шкалы палитры в виде градации ее цветов. Цветовое пространство палитры соответствует диапазону значений, заданному аргументом range, который может быть Full, Automatic или определяться функцией Range(real min, real max). Значение Full означает диапазон от минимального до максимального значений функции над интервалами разбиения, в то время как Automatic заботится об «эстетике» границ. Цветной график density plot может быть также получен с использованием массива данных: bounds image(picture pic=currentpicture, real[][] f, range range=Full, pair initial, pair final, pen[] palette, bool transpose=(initial.x < final.x && initial.y < final.y), bool copy=true, bool antialias=false); Если точка initial лежит левее и ниже final, то по умолчанию индексы массива интерпретируются как в декартовой системе координат (первый индекс – x, второй – y) в отличие от матричного порядка (первый индекс – y, второй – x). 109 Глава 9. Модуль palette 110 Чтобы построить график по массиву нерегулярно распределенных точек и массиву значений функции f в них, следует обратиться к одной из следующих процедур: bounds image(picture pic=currentpicture, pair[] z, real[] f, range range=Full, pen[] palette); bounds image(picture pic=currentpicture, real[] x, real[] y, real[] f, range range=Full, pen[] palette); Необязательная шкала палитры создается процедурой void palette(picture pic=currentpicture, Label L="", bounds bounds, pair initial, pair final, axis axis=Right, pen[] palette, pen p=currentpen, paletteticks ticks=PaletteTicks, bool copy=true, bool antialias=false); Цветовое пространство palette берется из bounds и шкалируется в соответствии со шкалированием оси Oz. Ориентация шкалы определяется параметром axis, который может принимать значения Right, Left, Top или Bottom. Шкала изображается прямоугольником с углами initial и final. Аргумент paletteticks представляет собой специальный тип отметок следующего вида: paletteticks PaletteTicks(Label format="", ticklabel ticklabel=null, bool beginlabel=true, bool endlabel=true, int N=0, int n=0, real Step=0, real step=0, pen pTick=nullpen, pen ptick=nullpen); И график, и шкала могут быть вставлены во фрейм и добавлены к рисунку в требуемое место. При необходимости может быть выполнено выравнивание. Пример 9.1 показывает, как конструируется график вместе со шкалой. Ïðèìåð 9.1. Ãðàôèê âèäà density plot ñ âåðòèêàëüíîé øêàëîé öâåòíîñòè. import graph; import palette; size(7.5cm,0); int n=256; real ninv=2pi/n; real[][] v=new real[n][n]; for(int i=0; i < n; ++i) for(int j=0; j < n; ++j) v[i][j]=sin(i*ninv)*cos(j*ninv); pen[] Palette=BWRainbow(); picture bar; bounds range= image(v,(0,0),(1,1),Palette); palette(bar,"$A$",range,(0,0), (0.4cm,4.3cm),Right,Palette, PaletteTicks("$%+#.1f$")); add(bar.fit(),point(E),30E); +1.0 +0.5 0.0 A −0.5 −1.0 В следующем примере 9.2 демонстрируется логарифмическая шкала цветности. Можно также создать рисунок, используя двумерный массив pen или функцию f типа pen: void image(picture pic=currentpicture, pen[][] data, pair initial, pair final, bool transpose=(initial.x < final.x && initial.y < final.y), bool copy=true, bool antialias=false); Глава 9. Модуль palette 111 Ïðèìåð 9.2. Ãðàôèê âèäà density plot ñ ãîðèçîíòàëüíîé ëîãàðèôìè÷åñêîé øêàëîé öâåòíîñòè. import graph; import palette; size(9cm,9cm,IgnoreAspect); real f(real x, real y) { return 0.9*pow10(2*sin(x/5+2*y^0.25)) + 0.1*(1+cos(10*log(y)));} scale(Linear,Log,Log); pen[] Palette=BWRainbow(); bounds range=image(f,Automatic,(0,1), (100,100),nx=200,Palette); xaxis("$x$",BottomTop,LeftTicks, above=true); yaxis("$y$",LeftRight,RightTicks, above=true); palette("$f(x,y)$",range,(0,200), (100,250),Top,Palette, PaletteTicks(ptick=linewidth( 0.5*linewidth()))); 10 −3 10 −2 f (x, y) 10 100 −1 101 102 80 100 102 y 101 100 0 20 40 x 60 void image(picture pic=currentpicture, pen f(int, int), int width, int height, pair initial, pair final, bool transpose= (initial.x < final.x && initial.y < final.y), bool antialias=false); Эти возможности представлены в примерах 9.3 и 9.4. Ïðèìåð 9.3. Èñïîëüçîâàíèå äâóìåðíîãî ìàññèâà òèïà pen. import palette; size(7cm,7cm,IgnoreAspect); int n=256; real ninv=2pi/n; pen[][] v=new pen[n][n]; for(int i=0; i < n; ++i) for(int j=0; j < n; ++j) v[i][j]=rgb(0.5*(1+sin(i*ninv)), 0.5*(1+cos(j*ninv)),0); image(v,(0,0),(1,1)); Для удобства в модуле palette определены операторы, которые могут быть использованы для создания массива перьев при заданных функции f и палитре palette: pen[] palette(real[] f, pen[] palette); Глава 9. Модуль palette Ïðèìåð 9.4. Èñïîëüçîâàíèå ôóíêöèè òèïà pen. import palette; size(7.5cm,7.5cm,IgnoreAspect); real fracpart(real x){ return (x-floor(x));} pair pws(pair z){ pair w=(z+exp(pi*I/5)/0.9)/ (1+z/0.9*exp(-pi*I/5)); return exp(w)*(w^3-0.5*I);} int N=512; pair a=(-1,-1); pair b=(0.5,0.5); real dx=(b-a).x/N; real dy=(b-a).y/N; pen f(int u, int v){ pair z=a+(u*dx,v*dy); pair w=pws(z); real phase=degrees(w,warn=false); real modulus=w == 0 ? 0: fracpart(log(abs(w))); return hsv(phase,1,sqrt(modulus));} image(f,N,N,(0,0),(300,300),antialias=true); pen[][] palette(real[][] f, pen[] palette); 112 Ãëàâà 10 Ìîäóëü contour Пакет предназначен для изображения линий уровня функций. Как известно, линией уровня функции z = f (x, y) называется кривая, определяемая уравнением f (x, y) = C, где C– произвольная константа, уровень функции. В Asymptote линии уровня функции f, заданной в прямоугольнике box(a,b), причем уровни хранятся в массивe ñ, строит процедура guide[][] contour(real f(real,real), pair a, pair b, real[] c, int nx=ngraph, int ny=nx, interpolate join=operator --, int subsample=1); Параметры nx и ny определяют разрешение. Разрешение по умолчанию – ngraph × ngraph (ngraph по умолчанию равно 100). Разрешение может быть увеличено для достижения более высокой точности. Оператором интерполяции по умолчанию является оператор operator -(прямая). Сплайн-интерполяция (operator ..) может обеспечить более плавные линии, но может и привести к переполнению. Параметр subsample указывает на число внутренних точек, которые должны быть использованы для построения экземпляров линий уровня внутри каждого квадрата 1 х 1; значения 1, принятого по умолчанию, обычно бывает достаточно. Сами линии уровня рисуют процедуры void draw(picture pic=currentpicture, Label[] L=new Label[], guide[][] g, pen p=currentpen); void draw(picture pic=currentpicture, Label[] L=new Label[], guide[][] g, pen[] p); В первом случае все линии уровня изображаются одним цветом, как в примере 10.1, во втором – разными, как в примере 10.2. Чтобы построить линии уровня для массива данных, заданных на равномерной двумерной решетке в прямоугольнике box(a,b), применяют оператор guide[][] contour(real[][] f, pair a, pair b, real[] c, interpolate join=operator --, int subsample=1); Построение линий уровня для массива данных, заданных на регулярных неперекрывающихся ячейках, определяемых двумерным массивом z, выполняет оператор guide[][] contour(pair[][] z, real[][] f, real[] c, interpolate join=operator --, int subsample=1); 113 Глава 10. Модуль contour 114 Ïðèìåð 10.1. Èçîáðàæåíèå ëèíèé óðîâíÿ îäíèì öâåòîì. import contour; size(7.5cm,0); real f(real x, real y) {return 0.5*x^2+y^2;} int n=5; real[] c=new real[n]; c[0]=0.1; c[1]=0.2; c[2]= 0.3; c[3]=0.4; c[4]=0.5; draw(contour(f,(-1,-1),(1,1),c),blue); Ïðèìåð 10.2. Èçîáðàæåíèå ëèíèé óðîâíÿ ðàçíûìè öâåòàìè. import contour; size(7.5cm,0); real f(real x, real y) {return 0.5*x^2+y^2;} int n=5; real[] c=new real[n]; pen[] p=new pen[n]; c[0]=0.1; c[1]=0.2; c[2]= 0.3; c[3]=0.4; c[4]=0.5; p[0]=red; p[1]=blue; p[2]=green; p[3]=orange; p[4]=Cyan; draw(contour(f,(-1,-1),(1,1),c),p); Чтобы построить линии уровня для массива значений функции f, определенных нерегулярно расположенными точками z, используют процедуру guide[][] contour(pair[] z, real[] f, real[] c, interpolate join=operator --); В следующем примере 10.3 линии уровня для функции z = x2 − y 2 не только изображаются, но и маркируются. Разрешение выбрано равным 100×100. Используется штриховая линия для отрицательных значений и сплошная – для положительных (и нуля). В примере 10.4 показано, как можно сочетать линии уровня с графиком типа density plot. Пример 10.5 демонстрирует конструирование линий уровня из нерегулярным образом заданных данных. Модуль contour может быть использован и для построения графика разрывной функции. Пусть, например, требуется изобразить график функции y= x2 −1 , − 4x + 3 Глава 10. Модуль contour 115 Ïðèìåð 10.3. Ìàðêèðîâêà ëèíèé óðîâíÿ. import contour; size(7.5cm,0); real f(real x, real y) {return x^2-y^2;} int n=10; real[] c=new real[n]; for(int i=0; i < n; ++i) c[i]=(i-n/2)/n; pen[] p=sequence(new pen(int i){ return (c[i] >= 0 ? solid : dashed)+fontsize(8pt); },c.length); Label[] Labels=sequence(new Label(int i) {return Label(c[i] != 0 ? (string) c[i] : "", Relative(unitrand()),(0,0), UnFill(1bp)); },c.length); draw(Labels,contour(f,(-1,-1),(1,1),c),p); -0.5 -0.1 -0.3 -0.2 -0.4 0.2 0.3 0.1 0.4 0.4 0.1 0.3 0.2 -0.2 -0.3 -0.1 -0.4 -0.5 Ïðèìåð 10.4. Êîìáèíàöèÿ ãðàôèêîâ density plot è ëèíèé óðîâíÿ. import graph; import palette; import contour; size(10cm,10cm,IgnoreAspect); pair a=(0,0); pair b=(2pi,2pi); real f(real x, real y){ f (x, y) return cos(x)*sin(y);} −1 −0.8 −0.6 −0.4 −0.2 0 0.2 0.4 0.6 0.8 1 int N=200; int Divs=10; int divs=2; defaultpen(1bp); pen Tickpen=black; pen tickpen=gray+0.5*linewidth(currentpen); 6 pen[] Palette=BWRainbow(); 5 bounds range=image(f,Automatic,a,b,N,Palette); 4 real[] Cvals=uniform(range.min,range.max,Divs); y 3 draw(contour(f,a,b,Cvals,N,operator--), Tickpen); 2 real[] cvals; for(int i=0; i < Cvals.length-1; ++i) 1 cvals.append(uniform(Cvals[i],Cvals[i+1], divs)[1:divs]); 0 0 1 2 3 4 5 6 draw(contour(f,a,b,cvals,N,operator--), x tickpen); xaxis("$x$",BottomTop,LeftTicks,above=true); yaxis("$y$",LeftRight,RightTicks,above=true); palette("$f(x,y)$",range,point(NW)+(0,0.5),point(NE)+(0,1),Top, Palette,PaletteTicks(N=Divs,n=divs,Tickpen,tickpen)); имеющей две точки разрыва второго рода: x = 1 и x = 3. Если попытаться сделать это средствами, рассмотренными в главе «Модуль graph», на дисплей будет выведена весьма затейливая кривая, ничего общего не имеющая с требуемым графиком. В примере 10.6 этому казусу соответствуют две «закомментированные» строчки программы. Выход из этой ситуации дает Глава 10. Модуль contour 116 Ïðèìåð 10.5. Íåðåãóëÿðíûå äàííûå. int n=180; real f(real a, real b) {return a^2+b^2;} srand(1); real r() {return 1.1*(rand()/randMax*2-1);} pair[] points=new pair[n]; real[] values=new real[n]; for(int i=0; i < n; ++i) { points[i]=(r(),r()); values[i]=f(points[i].x,points[i].y); } draw(contour(points,values,new real[]{0.25,0.5,1},operator ..),blue); изображение графика в виде линии уровня z = −1 функции z = y(x2 − 4x + 3). Пример 10.6 демонстрирует такую возможность. Ïðèìåð 10.6. Ãðàôèê ðàçðûâíîé ôóíêöèè. import graph; import contour; size(7.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); transform ec=scale(0.6); xaxis(L=Label(ec*"$x$",align=2E,mahogany), Ticks(ec*Label(black),NoZero, Size=2.5,size=1.5,mahogany),p=orange, Arrow(2mm,Fill(mahogany))); yaxis(L=Label(ec*"$y$",align=2N,mahogany), Ticks(ec*Label(black),NoZero, Size=2.5,size=1.5,mahogany),p=orange, Arrow(2mm,Fill(mahogany))); draw((1,-2.9)--(1,2.9),dashed+heavygreen); draw((3,-2.9)--(3,2.9),dashed+heavygreen); labelx(ec*"$O$",0,NW,mahogany); real F(real x, real y) {return y*(x^2-4*x+3);} draw(contour(F,(-2.8,-2.9),(6.8,2.9), new real[] {-1}),0.5bp+blue); //real f(real x) {return -1/(x^2-4*x+3);} //draw(graph(f,-2.8,6.8,operator..),0.5bp+blue); y 2 1 O −2 −1 x 1 2 3 4 5 6 −1 −2 Аналогичным образом может быть построен график неявно заданной функции. Представим, например, уравнение улитки Паскаля в виде (x2 + y 2 − 2x)2 − (x2 + y 2 ) = 0. Тогда, чтобы изобразить эту кривую, достаточно построить линию уровня z = 0 для функции z = (x2 + y 2 − 2x)2 − (x2 + y 2 ), как это показано на рис. примера 10.7. Глава 10. Модуль contour 117 Ïðèìåð 10.7. Ãðàôèê íåÿâíîé ôóíêöèè: óëèòêà Ïàñêàëÿ. import graph; import contour; size(7.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); xaxis(L=Label("$x$",align=2E,mahogany),p=orange, Arrow(2mm,Fill(mahogany))); yaxis(L=Label("$y$",align=2N,mahogany),p=orange, Arrow(2mm,Fill(mahogany))); real F(real x, real y) {return (x^2+y^2-2*x)^2-(x^2+y^2);} draw(contour(F,(-0.2,-2),(3.1,2), new real[] {0},operator ..),0.5bp+blue) y x Ãëàâà 11 Ìîäóëè markers, labelpath è patterns 11.1 Ìîäóëü markers Этот модуль содержит специализированные процедуры для маркирования путей и углов. Основной процедурой пакета является процедура markroutine markinterval(int n=1, frame f, bool rotated=false); которая помещает n копий фрейма f в центрах равномерно распределенных вдоль пути интервалов, при необходимости поворачивая их на угол, определяемый локальным положением касательной. Может быть использована процедура marker (см. раздел 8.2) для создания новых маркеров из следующих предопределенных фреймов: frame stickframe(int n=1, real size=0, pair space=0, real angle=0, pair offset=0, pen p=currentpen); frame circlebarframe(int n=1, real barsize=0, real radius=0, real angle=0, pair offset=0, pen p=currentpen, filltype filltype=NoFill, bool above=false); frame crossframe(int n=3, real size=0, pair space=0, real angle=0, pair offset=0, pen p=currentpen); frame tildeframe(int n=1, real size=0, pair space=0, real angle=0, pair offset=0, pen p=currentpen); Для удобства этот модуль содержит также маркеры StickIntervalMarker, CrossIntervalMarker, CircleBarIntervalMarker, TildeIntervalMarker из упомянутых фреймов. Рассмотрим их применение. Первый маркер рисует на пути черточки и определяется так: marker StickIntervalMarker(int i=2, int n=1, real size=0, real space=0, real angle=0, pair offset=0, bool rotated=true, pen p=currentpen, frame uniform=newframe, bool above=true); Из приводимых ниже примеров можно уяснить смысл параметров процедуры. Их описаний автор не нашел даже в исходниках, а взять на себя ответственность за их толкование не захотел. Пример 11.1 показывает применение рассмотренного маркера. Следующий маркер 118 Глава 11. Модули markers, labelpath и patterns 119 Ïðèìåð 11.1. Ïðèìåíåíèå ìàðêåðà StickIntervalMarker. import markers; size(7.5cm,0); path p=(0,0)--(1,0)--(2,0)--(3,0); transform T=shift(0,-0.5); draw(p,StickIntervalMarker(3,2,blue, dotframe(red))); p=T*p; draw(p,StickIntervalMarker(4,1,size=5mm, angle=-45,bp+.8green,dotframe(purple))); p=T*p; pen pn=linewidth(4bp); draw(p,pn,StickIntervalMarker(3,3,angle=25,pn,dotframe(red+pn))); marker CrossIntervalMarker(int i=2, int n=3, real size=0, real space=0, real angle=0, pair offset=0, bool rotated=true, pen p=currentpen, frame uniform=newframe, bool above=true); рисует крестики-звездочки, поворачивая их, если требуется на угол angle. Пример 11.2 демонстрирует некоторые его возможности. Ïðèìåð 11.2. Ïðèìåíåíèå ìàðêåðà CrossIntervalMarker. import markers; size(7.5cm,0); path p=(0,0)--(1,0)--(2,0)--(3,0); transform T=shift(0,-0.5); draw(p,CrossIntervalMarker(2,4,angle=0, bp+red,dotframe(purple))); p=T*p; draw(p,CrossIntervalMarker(3,3,bp+heavygreen, dotframe)); p=T*p; draw(p,CrossIntervalMarker(3,6,size=2mm, angle=45,bp+blue,dotframe(red))); Маркер marker CircleBarIntervalMarker(int i=2, int n=1, real barsize=0, real radius=0, real angle=0, pair offset=0, bool rotated=true, pen p=currentpen, filltype filltype=NoFill, bool circleabove=false, frame uniform=newframe, bool above=true); помечает путь кружочками: пустыми, заполненными, перечеркнутыми (с регулировкой угла перечеркивания), см. пример 13.18. Глава 11. Модули markers, labelpath и patterns 120 Ïðèìåð 11.3. Ïðèìåíåíèå ìàðêåðà CircleIntervalMarker. import markers; size(7.5cm,0); path p=(0,0)--(1,0)--(2,0)--(3,0); transform T=shift(0,-0.5); draw(p,CircleBarIntervalMarker(3,2, barsize=5mm,radius=1.5mm,angle=-45, darkgreen,filltype=NoFill, dotframe(purple))); p=T*p; draw(p,CircleBarIntervalMarker(n=3, barsize=8mm,radius=2mm, FillDraw(.8red),dotframe)); p=T*p; draw(p,CircleBarIntervalMarker(n=3,angle=30, barsize=8mm,radius=2mm,FillDraw(.8red),circleabove=true,dotframe)); Наконец, маркер marker TildeIntervalMarker(int i=2, int n=1, real size=0, real space=0, real angle=0, pair offset=0, bool rotated=true, pen p=currentpen, frame uniform=newframe, bool above=true); ставит на пути отметки в виде волнистых линий, которые можно использовать как сами по себе, так и образовывать из них новые фигуры. Результаты таких усилий демонстрирует пример 11.4. Ïðèìåð 11.4. Ïðèìåíåíèå ìàðêåðà TildeIntervalMarker. import markers; size(7.5cm,0); path p=(0,0)--(1,0)--(2,0)--(3,0); transform T=shift(0,-0.5); draw(p,TildeIntervalMarker(i=3,blue, dotframe(green))); p=T*p; draw(p,TildeIntervalMarker(i=3,n=2,red, angle=-20,dotframe(blue))); p=T*shift(relpoint(p,.5)+.65S)*scale(.5)* shift(-relpoint(p,.5))*rotate(45, relpoint(p,.15))*p; draw(p,TildeIntervalMarker(size=5mm,green, rotated=false,dotframe(red))); Если стандартные маркеры невыразительны, можно обратиться к более гибким возможностям, предоставляемым системой Asymptote. Некоторые из них показаны на рис. примера 11.5. Модуль также имеет в своем составе процедуру, которая маркирует угол AOB: void markangle(picture pic=currentpicture, Label L="", int n=1, real radius=0, real space=0, pair A, pair O, pair B, arrowbar arrow=None, pen p=currentpen, margin margin=NoMargin, marker marker=nomarker); Глава 11. Модули markers, labelpath и patterns 121 Ïðèìåð 11.5. Äðóãèå ìàðêèðîâêè. import markers; size(7.5cm,0); path p=(0,0)--(1,0)--(2,0)--(3,0); transform T=shift(0,-0.5); draw(p,marker(markinterval(3,dotframe, true))); p=T*p; draw(p,marker(stickframe,markuniform(4))); p=T*p; draw(p,marker(stickframe(red), markinterval(3,dotframe(blue),true))); p=T*p; frame cg; filldraw(cg,scale(5)*polygon(5),Yellow, bp+Magenta); marker markcg = marker(crossframe(n=4,size=1.5mm,bp+purple), markinterval(2,cg,true)); draw(p,markcg) В примере 11.6 эта процедура используется для обозначения внутреннего и внешнего углов, а в примере 14.32 – для обозначения различными способами углов треугольника. Ïðèìåð 11.6. Âíóòðåííèé è âíåøíèé óãëû. import markers; size(6cm,0); pair O=(0,0), A=(2,0), B=(1,1.5); draw(A--O--B,bp+blue); markangle(scale(1.5)*"$\alpha$",radius=40,A,O,B, ArcArrow,bp+red); markangle(scale(1.5)*"$\beta$",radius=30,B,O,A, BeginArcArrow(HookHead,2mm),bp+deepgreen); label("$O$",O,SW); label("$A$",A,SE);label("$B$",B,N); B α β O A 11.2 Ìîäóëü labelpath Процедура модуля с таким же названием позволяет придать метке форму заданного пути и расположить ее вдоль этого пути: void labelpath(picture pic=currentpicture, Label L, path g, string justify=Centered, pen p=currentpen); Параметр justify принимает значения LeftJustified, Centered или RightJustified. Компонента x преобразования shift, примененная к Label, интерпретируется как сдвиг вдоль кривой, а компонента y – как сдвиг в сторону от кривой. Другие преобразования метки игнорируются. Первый рис. примера 11.8 располагает метку прямо на кривой, второй рисует ее в форме кривой, но ниже нее. Глава 11. Модули markers, labelpath и patterns 122 Ïðèìåð 11.7. Óãëû òðåóãîëüíèêà. import markers; size(6cm,0); pair O=(0,0), A=(2,0), B=(1,1.5), C=A+dir(A--O,A--B); markangle(n=2,radius=20,A,O,B,p=bp+green, filltype=Fill(pink)); markangle(n=1,radius=20,O,B,A,p=bp+green, marker(markinterval(stickframe(n=2,4mm,0.5+red) ,true))); markangle(n=2,radius=20,B,A,O,p=bp+green, marker(markinterval(2,stickframe(n=1,2mm, 0.5bp+red),true))); draw(A--interp(A,C,1.68),brown+black); draw(A--O--B--cycle,bp+blue); label("$O$",O,SW); label("$A$",A,SE);label("$B$",B,N); B O A Ïðèìåð 11.8. Ìåòêà â ôîðìå ïóòè. is at est of curved be la Th at d e s t o f c u r ve in Asympt els o b la te is is n A sy m p ls i to te is Th import labelpath; size(7.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); path p=(-1,0)..(1,-0.7)..(1.5,-0.3)..(3,-.75); labelpath("This is a test of curved labels in Asymptote",p,blue); draw(p,red); path q=shift(0,-1)*p; labelpath(shift(0,-12)*"This is a test of curved labels in Asymptote",q,blue); draw(q,red); Часто приходится надписывать графики функций, размещая надпись вдоль графика. Результат такого оформления показан на рис. примера 11.9. 11.3 Ìîäóëü patterns Процедуры данного модуля позволяют создавать области, заполненные шаблонами (по-другому их называют ячеистыми, плиточными, или мозаичными структурами). Для этого используются образцы мозаик-картинок, имеющиеся в PostScript, которые Asymptote вызывает по их имени name. Добавление мозаик производится оператором void add(string name, picture pic, pair lb=0, pair rt=0); с необязательным указанием левого нижнего поля lb и правого верхнего rt. Чтобы заполнить рисунок мозаикой name, надо применить перо pattern("name"). Для получения мозаики из прямоугольников можно использовать процедуры picture tile(real Hx=5mm, real Hy=0, pen p=currentpen, filltype filltype=NoFill); Глава 11. Модули markers, labelpath и patterns 123 Ïðèìåð 11.9. Íàäïèñè âäîëü ãðàôèêà. import graph; import labelpath; size(7.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); pen penfill=rgb(0.85,1.0,0.85); real f1(real x) {return sqrt(4-x^2);} path grf1=graph(f1,-2,2); y real f2(real fi) {return 2-sin(fi);} path 2 2 grf2=polargraph(f2,pi,0,operator..); x +y =4 path grf=buildcycle(grf1,grf2); fill(grf,penfill); draw(grf,blue+1bp); D draw ((-2,0)--(2.3,0),orange,Arrow(size=3mm), 2 L=Label("$x$",mahogany, y )2 = 4(x2 + 2 2 +y + y) position=EndPoint,align=E)); (x draw ((0,0)--(0,2.7),orange,Arrow(size=3mm), O L=Label("$y$",mahogany, position=EndPoint,align=N)); label("$O$",(0,0),1.5*S,mahogany); label("$D$",(0.6,1.45)); label("$2$",(2,0),1.5*S); labelpath(Label(shift(0,5)*"$x^2+y^2=4$",Relative(0.3)),grf1); labelpath(Label(shift(0,-13)*"$(x^2+y^2+y)^2=4(x^2+y^2)$",align=2*S),grf2); 2 x picture checker(real Hx=5mm, real Hy=0, pen p=currentpen); picture brick(real Hx=5mm, real Hy=0, pen p=currentpen); В примере 11.10 показано заполнение кругов такого рода мозаикой. Ïðèìåð 11.10. Çàïîëíåíèå ïðÿìîóãîëüíîé ìîçàèêîé. import patterns; size(7.5cm,0); add("tile",tile()); add("filledtilewithmargin",tile(6mm,4mm, red,Fill),(1mm,1mm),(1mm,1mm)); add("checker",checker()); add("brick",brick()); real s=2.3; filldraw(unitcircle,pattern("tile")); filldraw(shift(s,0)*unitcircle, pattern("filledtilewithmargin")); filldraw(shift(2s,0)*unitcircle,pattern("checker")); filldraw(shift(3s,0)*unitcircle,pattern("brick")); Штриховые заполнения реализуются процедурами picture hatch(real H=5mm, pair dir=NE, pen p=currentpen); picture crosshatch(real H=5mm, pen p=currentpen); применение которых демонстрирует пример 11.11. Чтобы мозаика отображалась на экране корректно, приходится иногда отключать сглаживание в PostScript-просмотрщике. Глава 11. Модули markers, labelpath и patterns Ïðèìåð 11.11. Çàïîëíåíèå øòðèõîâêîé. import patterns; size(7.5cm,0); add("hatch",hatch(1mm,blue)); add("hatchback",hatch(2mm,NW,red)); add("crosshatch", crosshatch(3mm,heavygreen)); real s=1.25; filldraw(unitsquare,pattern("hatch")); filldraw(shift(s,0)*unitsquare, pattern("hatchback")); filldraw(shift(2s,0)*unitsquare,pattern("crosshatch")); 124 ×àñòü III Ðèñîâàíèå â ïðîñòðàíñòâå 125 Ãëàâà 12 Ïðîåêöèè, ïåðñïåêòèâà, îñâåùåíèå 12.1 Íàñòðîéêè 12.1.1 Èñïîëüçîâàíèå ôîðìàòà PRC Формат PRC (Product Representation Compact) является форматом, который можно использовать для встраивания трехмерных данных в pdf-файл. Отличается высокой степенью сжатия трехмерных моделей. Просмотр таких pdf-файлов возможен в программе Adobe Reader, начиная с версии 9.0. Основная цель использования формата PRC – внедрение в pdf-документ интерактивных изображений, анимации. Для изучения трехмерных тел бывает удобно рассматривать их, поворачивая, меняя точку обзора и т. п. Формат PRC встраивается в pdf-файл, если задана установка settings.prc=true; причем, ей должна предшествовать установка settings.outformat="pdf", расположенная в тексте перед командами типа import graph3 или import three. Если возможность интерактивности не используется, надо задать противоположную установку settings.prc=false; Если для большинства изображений предусмотрена интерактивность, а для какого-то изображения она не предусмотрена, то в начале кода для этого изображения следует поместить settings.embed=false; 12.1.2 Ðàçðåøåíèå èçîáðàæåíèÿ Команда settings.render управляет степенью разрешения изображения. Например, если установить settings.render=1; разрешение будет равно одному пикселю на один «большой пункт», см. п. 6.1.2. В общем случае установка setting render=n дает разрешение в n пикселей на bp. Отрицательное значение n в форматах eps и pdf интерпретируется как |2n|, для других форматов – как |n|. По умолчанию считается, что разрешение равно -1. Вообще рендерингом называется процесс создания растрового изображения (набора точекпикселей) из векторного. Для того чтобы отобразить векторное изображение 3d на экране, 126 Глава 12. Проекции, перспектива, освещение 127 оно должно пройти процедуру рендеринга. Обычно это делает видеокарта и ОС компьютера, более качественные рендеры – специальные программы (растеризаторы). Картинка подвергается рендерингу по умолчанию дважды, но это можно отменить установкой antialias=1. Высокое разрешение достигается дискретизацией (тайлингом, разбиением образа на мелкую плитку) изображения. Если позволит видеокарта, рендеринг можно сделать более эффективным, увеличив максимальный размер плитки maxtile до размеров экрана (указывается в виде maxtile=(0,0)). Если видеокарта создает нежелательные черные линии, можно попробовать установить горизонтальные и вертикальные компоненты maxtile меньшими, чем размеры экрана. Размер плитки также лимитируется величиной maxviewport, которая ограничивает максимальную ширину и высоту обзора. Увеличивая параметр render, можно получить более качественное изображение, но трансляция будет идти дольше и размер файла тоже увеличится. Второй способ улучшения качества изображения заключается в том, чтобы перейти от формата pdf к формату png, который используется для растровых изображений, увеличив значение settings.render. Этот способ обычно и рекомендуется. Основным его недостатком является то, что двумерные части изображения будут растрированы. Таким образом, этот метод не подходит для получения изображений, включающих как дву-, так и трехмерные картинки. Допускается применять значение settings.render=0. 12.1.3 Ðàçìåðû Автоматическое вычисление размеров картинки выполняется с помощью двух перерисовок. Максимальный размер рисунка может быть указан командой void size3(picture pic=currentpicture, real x, real y=x, real z=y, bool keepAspect=pic.keepAspect); Впрочем, на окончательные размеры рисунка влияют конфигурация параллелепипеда, в который помещают трехмерное изображение, точка обзора, дополнительные поля. Именно, чтобы получить 3D-версию фрейма (выполненную фактически в качестве 3Dкартинки), применяется метод линейного программирования. Результат затем вместе с результатом второй перерисовки вписывается в размеры обзора в соответствии с параметрами обычного двумерного размера картинки. При этом может быть использована глобальная переменная-пара viewportmargin, чтобы добавить горизонтальные или вертикальные поля к размерам обзора. В качестве альтернативы может быть установлено минимальное значение viewportsize. 12.2 Êîñîóãîëüíàÿ ïðîåêöèÿ Для обычных, не интерактивных, рисунков выбор точки обзора трехмерного изображения сродни искусству. Выбрать подходящую точку обзора помогает предопределенная переменная currentprojection. Отметим, что единичные векторы базиса трехмерной системы координат обозначаются в Asymptote как X, Y, Z, а начало координат – как O. Для вычерчивания поверхностей и тел вращения часто используется косоугольная проекция, которая в Asymptote выражается процедурой oblique(real angle); Глава 12. Проекции, перспектива, освещение 128 В этой проекции оси абсцисс и ординат направлены так же, как на плоском чертеже, а ось Oz кажется выходящей из этой плоскости в направлении наблюдателя. Трехмерная точка (x, y, z) проектируется в двумерную точку (x − 0.5z, y − 0.5z). Если задан необязательный параметр angle, ось Oz чертится под указанным (в градусах) углом к оси Ox (см. рис. 12.1). Проекция obliqueZ является синонимом проекции oblique. currentprojection = oblique; currentprojection = oblique(30); Рис. 12.1. Стандартная косоугольная проекция. Чтобы на наблюдателя была направлена ось Ox, используют проекцию obliqueX(real angle); при которой точка (x, y, z) проектируется в точку (y − 0.5x, z − 0.5x). Имеет место и угол поворота оси (рис. 12.2). currentprojection = obliqueX; currentprojection = obliqueX(30); Рис. 12.2. Вперед выходит ось Ox. Наконец, если требуется, чтобы ось Oy казалась выходящей из плоскости рисунка, применяют проекцию obliqueY(real angle); В этом случае точка (x, y, z) проектируется в точку (x + 0.5y, z + 0.5y). Используется и угол поворота оси (рис. 12.3). 12.3 Îðòîãîíàëüíàÿ ïðîåêöèÿ Эта проекция имитирует взгляд на объект из некоторой довольно удаленной точки пространства. Соответствующая процедура имеет вид Глава 12. Проекции, перспектива, освещение currentprojection = obliqueY; 129 currentprojection = obliqueY(30); Рис. 12.3. Ось Oy удаляется от нас. orthographic(triple camera, triple up=Z, triple target=O, real zoom=1, pair viewportshift=0, bool showtarget=true, bool center=false); Тип переменной triple, как мы помним, означает тройку чисел. Наблюдатель находится в бесконечно удаленной точке пространства и смотрит на изображение в направлении от точки camera к точке target. Опциональный аргумент up указывает, что отмеченный им вектор будет направлен вертикально вверх. По умолчанию вверх направлен координатный вектор Z. Параллельные прямые изображаются параллельными прямыми. Если showtarget=true, то границы рисунка раздвигаются, чтобы точка target попала в поле зрения. Если параметр center=true, то target оказывается в центре поля зрения. Аргумент zoom – коэффициент изменения размеров рисунка. Если одна из координат точки camera равна нулю, получаем проекцию изображения на соответствующую координатную плоскость (рис. 12.4). camera = (5,2,3) camera = (5,2,0) Рис. 12.4. Ортогональная проекция. Применение проекции orthographic(real x, real y, real z, triple up=Z, triple target=O, real zoom=1, pair viewportshift=0, bool showtarget=true, bool center=false); Глава 12. Проекции, перспектива, освещение 130 эквивалентно orthographic((x,y,z),up,target,zoom,viewportshift,showtarget,center); В Asymptote определены стандартные точки обзора, используемые в техническом черчении: projection projection projection projection projection projection LeftView=orthographic(-X,showtarget=true); RightView=orthographic(X,showtarget=true); FrontView=orthographic(-Y,showtarget=true); BackView=orthographic(Y,showtarget=true); BottomView=orthographic(-Z,showtarget=true); TopView=orthographic(Z,showtarget=true); Процедура triple camera(real alpha, real beta); может быть использована для определения положения камеры для оси Ox, расположенной ниже горизонтали под углом alpha; оси Oy, расположенной вниз от горизонтали под углом beta; и оси Oz, направленной вверх. 12.4 Ïåðñïåêòèâà При использовании перспективы возникает эффект «реалистичности»: более близкие объекты кажутся бо́льшими. При этом обозначения осей координат и другие надписи на рисунке также выглядят бо́льшими, если они расположены ближе; в зависимости от контекста, это может быть как полезной особенностью, так и досадным артефактом. Процедура для создания подходящей перспективы выглядит следующим образом: perspective(triple camera, triple up=Z, triple target=O, real zoom=1, real angle=0, pair viewportshift=0, bool showtarget=true, bool autoadjust=true, bool center=autoadjust); Проектирование выполняется с учетом того, что обзор направлен от точки camera к точке target, причем, вектор up направлен вверх (рис. 12.5). Если render=0, проекция трехмерных кубических сплайнов Безье осуществляется с помощью их аппроксимации двумерными неоднородными рациональными B-сплайнами (NURBS), которые реализуют двумерную кривую Безье, содержащую дополнительные узлы и опорные точки. Если autoadjust=true, camera будет автоматически удерживаться за пределами ограничивающего объект параллелепипеда при любых интерактивных вращениях вокруг target. Если center=true, точка target будет располагаться в центре ограничивающего параллелепипеда. Применение проекции perspective(real real bool bool x, real y, real z, triple up=Z, triple target=O, zoom=1, real angle=0, pair viewportshift=0, showtarget=true, bool autoadjust=true, center=autoadjust); Глава 12. Проекции, перспектива, освещение up = Z 131 up = X + Z Рис. 12.5. Перспектива. равносильно perspective((x,y,z),up,target,zoom,angle,viewportshift,showtarget, autoadjust,center); По умолчанию currentprojection=perspective(5,4,2). 12.5 Îñâåùåíèå ïîâåðõíîñòè Освещение трехмерных объектов регулируется с помощью структуры light: struct light{real[][] diffuse; real[][] ambient; real[][] specular; pen background=nullpen; specularfactor; bool viewport; triple[] position; transform3 T=identity(4); bool on() {return position.length > 0;} }; Здесь diffuse – отраженный от поверхности рассеянный свет; ambient – фоновое освещение, заполняющее все пространство; specular – зеркальное освещение, исходящее из определенной точки пространства и отражающееся от поверхности в определенном направлении; background – цвет фона трехмерной канвы; specularfactor – степень освещенности объекта; viewport отмечает, движется ли источник света вместе с движением камеры; position – координаты направленных источников света. В Asymptote имеются предопределенные типы освещений: Viewport, White, Headlamp и nolight (рис. 12.6). Последний тип самый простой – никакого освещения. Остальные три определяются следующим образом: light Viewport=light(ambient=gray(0.1), specularfactor=3, viewport=true, (0.25,-0.25,1)); light White=light(new pen[] {rgb(0.38,0.38,0.45),rgb(0.6,0.6,0.67), Глава 12. Проекции, перспектива, освещение 132 Headlamp White Viewport nolight light((0,0,3)); light((0,0,3),(0,0,-3)); Рис. 12.6. Виды освещений. rgb(0.5,0.5,0.57)}, specularfactor=3, new triple[] {(-2,-1.5,-0.5),(2,1.1,-2.5),(-0.5,0,2)}); light Headlamp=light(gray(0.8), ambient=gray(0.1), specular=gray(0.7), specularfactor=3, viewport=true, dir(42,48)); По умолчанию предполагается, что currentlight=Headlamp. На двух последних картинках рис. 12.6 показано, как можно определить собственные источники освещения, расположенные в заданных точках. Имеется возможность детальнее установить свойства освещения, хотя это требует более глубокого проникновения в суть вопроса и множества проб и ошибок! В большинстве случаев для рисования в пространстве используются те же перья, что и для рисования на плоскости. Впрочем, для особых случаев имеется расширение понятия пера под названием material. Такое перо несет в себе три сорта света, прозрачность и отблеск. В общем случае material определяется следующим образом: material material(pen diffusepen=black, pen ambientpen=black, pen emissivepen=black, pen specularpen=mediumgray, real opacity=opacity(diffusepen), real shininess=defaultshininess); Ãëàâà 13 Ìîäóëü three 13.1 Êîíòóðû è ïîâåðõíîñòè Этот модуль расширяет конструкции плоской геометрии на трехмерное пространство. Команды guide и path становятся командами guide3 и path3, а, кроме того, появляется новая процедура surface, описывающая поверхности. Вместо координат типа pair теперь повсеместно используются тройки triple. Точки и метки изображаются в пространстве с помощью тех же самых операторов, dot и label, которыми пользуются для рисования на плоскости. Но вот стрелки становятся объемными и для их рисования существует специальная процедура Arrow3, о чем речь еще впереди. Имеются два предопределенных пути unitcircle3 и unitsquare3, которые представляют собой единичную окружность и единичный квадрат: path3 unitcircle3 = X..Y..-X..-Y..cycle; path3 unitsquare3 = O--X--X+Y--Y--cycle; На рис. примера 13.1 можно увидеть построение путей в пространстве. Ïðèìåð 13.1. Ïóòè â ïðîñòðàíñòâå. import three; size(6.5cm,0); draw(unitcircle3,heavygreen); draw(((-1,-1,0)--(1,-1,0)--(1,1,0)-(-1,1,0)--cycle),brown); path3 gg=(1,0,0)..(0,1,1)..(-1,0,0).. (0,-1,1)..cycle; draw(gg,blue); dot(gg,red); Модуль three поддерживает преобразования двумерных путей в трехмерные и наоборот: path3 path3(path p, triple plane(pair)=XYplane); path path(path3 p, pair P(triple)=xypart); Из замкнутого пути path3 можно получить поверхность, натянутую на этот путь, с помощью процедуры surface surface(path3 external, triple[] internal=new triple[], triple[] normals=new triple[], pen[] colors=new pen[], bool3 planar=default); 133 Глава 13. Модуль three 134 Чтобы нарисовать поверхность, следует применить одну из следующих процедур рисования: void draw(picture pic=currentpicture, surface s, int nu=1, int nv=1, material surfacepen=currentpen, pen meshpen=nullpen, light light=currentlight, light meshlight=light, string name="", render render=defaultrender); void draw(picture pic=currentpicture, surface s, int nu=1, int nv=1, material[] surfacepen, pen meshpen, light light=currentlight, light meshlight=light, string name="", render render=defaultrender); void draw(picture pic=currentpicture, surface s, int nu=1, int nv=1, material[] surfacepen, pen[] meshpen=nullpens, light light=currentlight, light meshlight=light, string name="", render render=defaultrender); Параметры nu и nv определяют число подобластей, необходимых при рисовании необязательных линий сетки на поверхности для получения ячеек Безье. Параметр meshpen означает перо, изображающее линии сетки; его толщину можно изменить с помощью функций thin() (тонкое перо) , thick() (толстое перо) или, просто добавив к перу толщину линии bp, умноженную на подходящий коэффициент. Необязательный параметр name используется в качестве префикса для именования ячеек дерева PRC-модели. В простейшем случае поверхность создается и рисуется, как в примере 13.2. Ïðèìåð 13.2. Êîíñòðóèðîâàíèå ïîâåðõíîñòè èç êðèâîé. import three; size(6.5cm,0); path3 g=(1,0,0)..(0,1,1)..(-1,0,0).. (0,-1,1)..cycle; draw(surface(g),yellow); draw(((-1,-1,0)--(1,-1,0)--(1,1,0)-(-1,1,0)--cycle),blue); dot(g,red); В примере 13.3 сетка на поверхности состоит из 5 линий по каждому направлению, причем рисуется коричневым утолщенным пером, в то время как поверхность изображается желтым цветом. Ïðèìåð 13.3. Ñåòêà 5 õ 5 íà ïîâåðõíîñòè. import three; currentprojection=orthographic(3,1,1); size(6.5cm,0); path3 g=(1,0,0)..(0,1,1)..(-1,0,0)..(0,-1,1).. cycle; draw(surface(g),nu=5,nv=5,yellow,brown+0.4bp); draw(((-1,-1,0)--(1,-1,0)--(1,1,0)--(-1,1,0)-cycle),blue); dot(g,red); Глава 13. Модуль three 135 В следующем примере 13.4 количество линий сетки вдоль одного из направлений увеличено в три раза и выбраны другие цвета для изображения сетки и поверхности. Ïðèìåð 13.4. Ñåòêà 5 õ 15. import three; currentprojection=orthographic(3,1,1); size(6.5cm,0); path3 g=(1,0,0)..(0,1,1)..(-1,0,0)..(0,-1,1).. cycle; draw(surface(g),nu=5,nv=15,orange,black+0.4bp); draw(((-1,-1,0)--(1,-1,0)--(1,1,0)--(-1,1,0)-cycle),blue); dot(g,red); Один из вариантов оператора draw для изображения поверхностей (см. выше) позволяет применить несколько цветов для рисования как поверхности, так и сетки на ней. Пример 13.5 демонстрирует использование такой возможности. Ïðèìåð 13.5. Èñïîëüçîâàíèå ìàññèâîâ öâåòîâ. import three; currentprojection=orthographic(3,1,1); size(6.5cm,0); pen[] p1={paleblue,darkblue}, p2={palered,red,brown,darkbrown}; path3 g=(1,0,0)..(0,1,1)..(-1,0,0)..(0,-1,1).. cycle; draw(surface(g),nu=10, nv=10, p1,p2+thick()); draw((-1,-1,0)--(1,-1,0)--(1,1,0)--(-1,1,0)-cycle,blue); dot(g,red); В Asymptote имеются предопределенные поверхности: единичная сфера – unitsphere, единичная полусфера – unithemisphere, единичный круг – unitdisk, единичный квадрат – unitplane, единичный куб – unitcube, единичный цилиндр – unitcylinder, единичный конус-поверхность – unitcone, единичный конус-тело – unitsolidcone. Все эти поверхности показаны на рис. 13.6-13.13. Ïðèìåð 13.6. Åäèíè÷íàÿ ñôåðà. import three; size(4.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); draw(unitsphere,blue); draw(O--1.5X,L=Label("$x$",EndPoint,WSW,mahogany),orange); draw(O--1.3Y,L=Label("$y$",EndPoint,ESE,mahogany),orange); draw(O--1.2Z,L=Label("$z$",EndPoint,N,mahogany),orange); Глава 13. Модуль three 136 Ïðèìåð 13.7. Åäèíè÷íàÿ ïîëóñôåðà. import three; size(4.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); draw(unithemisphere,red); draw(O--1.5X,L=Label("$x$",EndPoint,WSW,mahogany),orange); draw(O--1.3Y,L=Label("$y$",EndPoint,ESE,mahogany),orange); draw(O--1.2Z,L=Label("$z$",EndPoint,N,mahogany),orange); Ïðèìåð 13.8. Åäèíè÷íûé êðóã. import three; size(4.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); draw(unitdisk,green); draw(O--1.5X,L=Label("$x$",EndPoint,WSW,mahogany),orange); draw(O--1.3Y,L=Label("$y$",EndPoint,ESE,mahogany),orange); draw(O--1.2Z,L=Label("$z$",EndPoint,N,mahogany),orange); Ïðèìåð 13.9. Åäèíè÷íûé êâàäðàò. import three; size(4.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); draw(unitplane,cyan); draw(O--1.5X,L=Label("$x$",EndPoint,WSW,mahogany),orange); draw(O--1.3Y,L=Label("$y$",EndPoint,ESE,mahogany),orange); draw(O--1.2Z,L=Label("$z$",EndPoint,N,mahogany),orange); Ïðèìåð 13.10. Åäèíè÷íûé êóá. import three; size(4.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); draw(unitcube,yellow); draw(O--1.5X,L=Label("$x$",EndPoint,WSW,mahogany),orange); draw(O--1.3Y,L=Label("$y$",EndPoint,ESE,mahogany),orange); draw(O--1.2Z,L=Label("$z$",EndPoint,N,mahogany),orange); Следующий пример 13.14 показывает, как создается плоская область, которая закрашивается четырьмя цветами. Вообще, плоские поверхности Безье конструируются с помощью процедуры Ореста Шардта bezulate, которая осуществляет декомпозицию областей, ограниченных (в соответствии с правилом заполнения zerowinding) простыми контурами (пересекающимися лишь на границах) на подобласти, ограниченные контурами длины 4 или меньше. Более эффективной является процедура, предназначенная для изображения пространственных мозаик, скомпонованных из множества треугольников, в которой можно задать вер- Глава 13. Модуль three 137 Ïðèìåð 13.11. Åäèíè÷íûé öèëèíäð. import three; size(4.5cm,0); pen mahogany=cmyk(0,0.85,0.87,0.35); draw(unitcylinder,magenta); draw(O--1.5X,L=Label("$x$",EndPoint,WSW,mahogany),orange); draw(O--1.3Y,L=Label("$y$",EndPoint,ESE,mahogany),orange); draw(O--1.2Z,L=Label("$z$",EndPoint,N,mahogany),orange); Ïðèìåð 13.12. Åäèíè÷íûé êîíóñ (ïîâåðõíîñòü). import three; size(4.5cm,0); currentprojection=perspective(5,4,-1.5); pen mahogany=cmyk(0,0.85,0.87,0.35); draw(unitcone,surfacepen=material(white,emissivepen=royalblue)); draw(O--1.5X,L=Label("$x$",EndPoint,WSW,mahogany),orange); draw(O--1.3Y,L=Label("$y$",EndPoint,ESE,mahogany),orange); draw(O--1.2Z,L=Label("$z$",EndPoint,N,mahogany),orange); Ïðèìåð 13.13. Åäèíè÷íûé êîíóñ (òåëî). import three; size(4.5cm,0); currentprojection=perspective(5,4,-1.5); pen mahogany=cmyk(0,0.85,0.87,0.35); draw(unitsolidcone,surfacepen=material(white, emissivepen=royalblue); draw(O--1.5X,L=Label("$x$",EndPoint,WSW,mahogany),orange); draw(O--1.3Y,L=Label("$y$",EndPoint,ESE,mahogany),orange); draw(O--1.2Z,L=Label("$z$",EndPoint,N,mahogany),orange); Ïðèìåð 13.14. Çàïîëíåíèå ïëîñêîé îáëàñòè ðàçëè÷íûìè öâåòàìè. import three; size(6cm,0); path3 g=X..Y..-X..-Y..cycle; draw(surface(g,new pen[] {red,green,blue,black}),nolight); шины треугольников и цвета, отнесенные к этим вершинам. void draw(picture pic=currentpicture, triple[] v, int[][] vi, triple[] n={}, int[][] ni={}, material m=currentpen, pen[] p={}, int[][] pi={}, light light=currentlight); Здесь массив v является перечнем всех вершин, а массив vi состоит из подмассивов длины 3, содержащих элементы v, соответствующие вершинам каждого треугольника. Подобным же образом необязательные аргументы n и ni содержат данные о нормалях, а p и pi – о перьях, привязанных к вершинам. Этот подход демонстрирует пример 13.15. Глава 13. Модуль three 138 Ïðèìåð 13.15. Òðèàíãóëÿöèÿ îáëàñòåé. import three; size(7.5cm); triple[] v={O,X,X+Y,Y}; triple[] n={Z,X}; int[][] vi={{0,1,2},{2,3,0}}; int[][] ni={{0,0,0},{1,1,1}}; pen[] p={red+opacity(0.5),green+opacity(0.5), blue+opacity(0.5),black+opacity(0.5)}; int[][] pi={{0,1,2},{2,3,0}}; draw(v,vi,n,ni,red); draw(v+0.7Z,vi,p,pi); 13.2 Ñòðåëêè Трехмерные версии стрелок и черточек на кривых и прямых можно нарисовать с помощью спецификаторов None, Blank, BeginBar3, EndBar3 (равносильно Bar3), Bars3, BeginArrow3, MidArrow3, EndArrow3 (равносильно Arrow3), Arrows3, BeginArcArrow3, EndArcArrow3 (равносильно ArcArrow3), MidArcArrow3 и ArcArrows3. Для черточек допускаются необязательные аргументы (real size=0, triple dir=O). Если size=0, используется длина черточки по умолчанию; если dir=O, черточка изображается перпендикулярно пути и в соответствии с исходным направлением обзора. Предопределенные острия стрелок называются DefaultHead3, HookHead3 и TeXHead3. Используются также двумерные острия, ориентированные соответственно исходной точке обзора (или вектором normal): DefaultHead2(triple normal=O), HookHead2(triple normal=O), TeXHead2(triple normal=O). Перечисленные возможности представлены в примерах 13.16 и 13.17 Ïðèìåð 13.16. Âèäû ñòðåëîê è ÷åðòî÷åê. import three; size(0,5cm); currentprojection=obliqueX; defaultpen(0.22mm); draw((0,0,5)--(0,1.5,5),Arrow3(DefaultHead3,red)); draw((0,0,4)--(0,1.5,4),Arrow3(HookHead3)); draw((0,0,3)--(0,1.5,3),Arrow3(TeXHead3)); draw((0,0,2)--(0,1.5,2),Arrows3(red)); draw((0,0,1)--(0,1.5,1),BeginArrow3(red)); draw((0,0,0)--(0,1.5,0),MidArrow3(red)); draw((0,2,5)--(0,3.5,5),Arrow3(red)); draw((0,2,4)--(0,3.5,4),ArcArrow3(red)); draw((0,2,3)--(0,3.5,3),Bar3); draw((0,2,2)--(0,3.5,2),Bars3); draw((0,2,1)--(0,3.5,1),BeginBar3); draw((0,2,0)--(0,3.5,0),EndBar3); В модуле three определены также трехмерные поля NoMargin3, BeginMargin3, EndMargin3, Margin3, Margins3, BeginPenMargin2, EndPenMargin2, PenMargin2, PenMargins2, BeginPenMargin3, EndPenMargin3, Глава 13. Модуль three 139 Ïðèìåð 13.17. Ñòðåëêè íà êðèâûõ. import three; size(12cm,0); defaultrender.merge=true;defaultpen(0.53mm); currentprojection=perspective(24,4,12); currentlight=light(gray(0.5),specularfactor=3,viewport=false, (0.5,-0.5,-0.25),(0.5,0.5,0.25),(0.5,0.5,1),(-0.5,-0.5,-1)); path3 g1=(-0.2,0,0)..(0.3,0.1,0)..(0.3,0.5,0); draw(g1,blue,Arrows3(TeXHead3),currentlight); path3 g2=(-0.4,-0.3,0) ..(0.6,0.2,0) ..(0.6,1,0); draw(g2,green, ArcArrows3( HookHead3), currentlight); path3 g3=(-0.6,-0.6,0) ..(0.9,0.3,0) ..(0.9,1.5,0); draw(g3,red, Arrows3( DefaultHead3), currentlight); path3 g4=(-0.8,-0.9,0)..(1.2,0.4,0)..(1.2,2,0); draw(g4,blue,Arrows3(TeXHead2),currentlight); path3 g5=(-1,-1.2,0)..(1.5,0.5,0)..(1.5,2.5,0); draw(g5,green,ArcArrows3(HookHead2,NoFill),currentlight); path3 g6=(-1.2,-1.5,0)..(1.8,0.6,0)..(1.8,3,0); draw(g6,red,Arrows3(DefaultHead2(normal=Z)),currentlight); PenMargin3, PenMargins3, BeginDotMargin3, EndDotMargin3, DotMargin3, DotMargins3, Margin3 TrueMargin3. 13.3 Îêðóæíîñòè, äóãè, ïðÿìîóãîëüíèêè è áîêñû Кроме упомянутой ранее единичной окружности unitcircle3, Asymptote умеет строить окружность произвольного радиуса r с центром в точке c в плоскости, расположенной перпендикулярно вектору normal: path3 circle(triple c, real r, triple normal=Z); По умолчанию плоскость окружности перпендикулярна вектору Z. Пример 13.18 показывает применение этой процедуры для рисования в трехмерном пространстве. Дуга в пространстве определяется в сферической системе координат x = ρ cos ϕ sin θ, y = ρ sin ϕ sin θ, z = ρ cos θ, ρ ≥ 0, −π ≤ ϕ < π, 0 ≤ θ ≤ π, Глава 13. Модуль three 140 Ïðèìåð 13.18. Îêðóæíîñòè â ïðîñòðàíñòâå. import three; size(7.5cm,0); currentprojection=perspective( camera=(6.36,-0.68,2.026), up=(-0.0038,-0.0037,0.011), target=(-0.0022,-0.0017,0.0089), zoom=1, angle=12.64, autoadjust=false); dot(O); path3 c1=unitcircle3; draw(c1, blue); draw(surface(c1), purple+opacity(0.5)); path3 c2=circle(O,0.75,Z-Y); draw(c2,orange); draw(surface(c2), yellow+opacity(0.5)); path3 c2=circle(O,0.75,Y); draw(c2,orange); draw(surface(c2),yellow+opacity(0.5)); path3 c2=circle(O,0.75,Z+Y); draw(c2,orange); draw(surface(c2),yellow+opacity(0.5)); с помощью процедуры path3 arc(triple c, real r, real theta1, real phi1, real theta2, real phi2, triple normal=O); Здесь c – центр окружности дуги, r – ее радиус, причем дуга проводится против часовой стрелки от точки c+r*dir(theta1,phi1) к точке c+r*dir(theta2,phi2) относительно векторного произведения cross(dir(theta1,phi1),dir(theta2,phi2)), если theta2>=theta1 и phi2>=phi1. Угол theta откладывается от оси Oz, а угол phi – от оси Ox. Параметр normal обязательно указывается, если радиус-векторы точки c и концов дуги коллинеарны. Рис. примера 13.19 демонстрирует и сферическую систему координат, и пример применения процедуры arc. Следует обратить внимание на вычисление координат точки A и ее проекции на плоскость xOy в сферической системе координат. Дополнительно можно указать значения параметра CW (по часовой стрелке) или CCW (против часовой стрелке), см. пример 13.20. Можно также построить дугу с центром в точке c от заданной точки v1 до другой заданной точки v2, используя следующую модификацию процедуры arc: path3 arc(triple c, triple v1, triple v2, triple normal=O, bool direction=CCW); Предполагается выполненным условие |v1-c|=|v2-c|. Рис. примера 13.21 показывает такое построение дуги «от точки до точки». Часто удобно вычислять координаты точек с помощью выражения r*dir(phi,theta), что продемонстрировано в примере 13.22. Прямоугольник в пространстве создает процедура path3 plane(triple u, triple v, triple O=O); Этот прямоугольник лежит в плоскости с нормалью cross(u,v) (т. о., u, v являются направляющими векторами плоскости) и представляет собой путь O--O+u--O+u+v--O+v--cycle, проходящий через начало координат O. В примере 13.23 с помощью таких прямоугольников создается параллелепипед. Глава 13. Модуль three 141 Ïðèìåð 13.19. Ïîñòðîåíèå äóã â ïðîñòðàíñòâå. import three; size(7.5cm,0); currentprojection=obliqueX; draw(O--2*X,Arrow3);label("$x$",2*X,NW); draw(O--2*Y,Arrow3);label("$y$",2*Y,SE); draw(O--2*Z,Arrow3);label("$z$",2*Z,E); real theta1=0, theta2=40; real phi1=0, phi2=60; path3 arc1=arc(O,1.5,theta1,phi2,theta2,phi2); draw(arc1,red,Arrow3); draw(arc(O,1.5,theta2,phi2,90,phi2), red+dotted); path3 arc2=arc(O,1,90,phi1,90,phi2); draw(arc2,blue,Arrow3); triple pA=(1.5*Cos(phi2)*Sin(theta2), 1.5*Sin(phi2)*Sin(theta2), 1.5*Cos(theta2)); triple projA=(1.5*Cos(phi2)*Cos(90-theta2), 1.5*Sin(phi2)*Cos(90-theta2),0); dot("$A$",pA,NE); dot(projA); draw(O--pA--projA--cycle, dashed); draw(projA--(1.5*Cos(phi2),1.5*Sin(phi2),0), red+dotted); label("$\varphi$",(Cos(phi2/2),Sin(phi2/2),0),SE); label("$\theta$",(1.5*Cos(phi2)*Sin(theta2/2),1.5*Sin(phi2)*Sin(theta2/2), 1.5*Cos(theta2/2)),N); Ïðèìåð 13.20. Ïîñòðîåíèå äóã ïî è ïðîòèâ ÷àñîâîé ñòðåëêè. import three; size(7.5cm,0); currentprojection=obliqueX; draw(O--2*X,Arrow3);label("$x$",2*X,NW); draw(O--2*Y,Arrow3);label("$y$",2*Y,SE); draw(O--Z,Arrow3);label("$z$",Z,E); path3 arc1=arc(O,1,90,0,90,60,CCW); draw(arc1,blue,Arrow3); path3 arc2=arc(O,1,90,0,90,60,CW); draw(arc2,red,Arrow3); draw(O--(cos(pi/3),sin(pi/3),0),dotted); Превращая прямоугольники в плоские поверхности, то есть закрашивая их, можно получить кусочки плоскостей, как в примере 13.24. Трехмерный бокс с противоположными вершинами в точках v1 и v2 можно получить с помощью функции path3[] box(triple v1, triple v2); В частности, предопределенный единичный бокс задается как path3[] unitbox=box(O,(1,1,1)); Глава 13. Модуль three 142 Ïðèìåð 13.21. Ïîñòðîåíèå äóãè îò îäíîé òî÷êè äî äðóãîé. import three; size(5cm,0); currentprojection=oblique; draw(O--2*X,Arrow3,blue); triple pA, v1, v2; pA=(1,0,0); v1=(1,sqrt(2)/4,-sqrt(2)/4); v2=(1,sqrt(2)/4,sqrt(2)/4); path3 arc1=arc(pA,v1,v2,CW); draw(arc1,red,Arrow3); Ïðèìåð 13.22. Âû÷èñëåíèå êîîðäèíàò òî÷åê ñ ïîìîùüþ ïðîöåäóðû dir. import three; size(5.5cm,0); currentprojection=perspective(4,1,2); real radius=1, theta=37, phi=60; real r=1.2; draw(Label("$x$",1),O--0.9*X,Arrow3(HookHead3)); draw(Label("$y$",1),O--r*Y,Arrow3(HookHead3)); draw(Label("$z$",1),O--r*Z,Arrow3(HookHead3)); label("$\rm O$",(0,0,0),W); triple pQ=radius*dir(theta,phi); draw(O--radius*dir(90,phi)^^O--pQ,dashed); dot("$R*\mathrm{dir}\left(\theta,\phi\right)$",pQ); draw("$\theta$",reverse(arc(O,0.5*pQ,0.5*Z)),N+0.3E, Arrow3(HookHead2)); draw("$\varphi$",arc(O,0.5*X,0.5*(pQ.x,pQ.y,0)), N+0.3E,Arrow3(HookHead2)); path3 p3=O--arc(O,radius,0,phi,90,phi)--cycle; draw(surface(p3),lightblue+opacity(0.2)); Ïðèìåð 13.23. Ñîñòàâëåííûé èç ïðÿìîóãîëüíèêîâ ïàðàëëåëåïèïåä. import three; size(5.5cm); currentprojection=oblique; triple u,v,w; u=(1,1,1); v=(1,-1,1); w=cross(u,v); path3 pl1=plane(u,v,O); path3 pl2=plane(u,w,O); path3 pl3=plane(u,w,v); path3 pl4=plane(u,v,w); draw(pl1,red); draw(pl2,blue); draw(pl3,cyan); draw(pl4,green); Практическое построение таких параллелепипедов показано в примере 13.25. 13.4 Òðåõìåðíûå ïðåîáðàçîâàíèÿ Asymptote предлагает для использования в построении рисунков несколько аффинных преобразований трехмерного пространства, относящихся к типу transform3. Тождественное преобразование обозначается identity(4). Глава 13. Модуль three 143 Ïðèìåð 13.24. Çàêðàøåííûå ïðÿìîóãîëüíèêè. import three; size(5.5cm,0); currentprojection=orthographic( camera=(0.011,0.0023,0.0023), up=(0.0058,0.0008,0.015), target=(0,0,0),zoom=1); path3 pl1=plane(Y,Z,O); path3 pl2=plane(Y,Z,X); path3 pl3=plane(X,Z,(X+Y)/2); draw(surface(pl1),green); draw(surface(pl2),blue+opacity(0.6)); draw(surface(pl3),red+opacity(0.6)); Ïðèìåð 13.25. Áîêñû. import three; size(5.5cm,0); draw(O--X,red+linewidth(0.7pt),Arrow3); draw(O--Y,red+linewidth(0.7pt),Arrow3); draw(O--Z,red+linewidth(0.7pt),Arrow3); label("$\mathbf{i}$",0.5*X,SE); label("$\mathbf{j}$",0.5*Y,SW); label("$\mathbf{k}$",0.5*Z,SW); draw(unitbox,red); draw(box(O,2*(X+Y+Z)),blue); Преобразование сдвига, или перемещения, выполняется с помощью оператора transform3 shift(triple v) который перемещает геометрический объект на вектор v. В примере 13.26 показано перемещение как точек, так и сложного геометрического объекта (треугольника). Для масштабирования объекта применяется несколько операторов. Масштабирование по соответствующим координатным осям осуществляется с помощью операторов transform3 xscale3(real x) transform3 yscale3(real y) transform3 zscale3(real z) Первый из них масштабирует в x раз вдоль оси Ox, второй – в y раз вдоль оси Oy, а третий – в z раз вдоль оси Oz. Применение всех трех преобразований демонстрирует пример 13.27, в котором выполняется растяжение единичного цилиндра unitcylinder (изображенного в верхней части рис.) поочередно вдоль каждой из координатных осей. Оператор transform3 scale3(real s) Глава 13. Модуль three 144 Ïðèìåð 13.26. Îïåðàòîð ñäâèãà (ïåðåìåùåíèÿ). import three; size(6cm); currentprojection=obliqueX; triple u=(-1.5,0.75,0), v=(0,-0.5,-1); transform3 t1=shift(u); transform3 t2=shift(v); triple pA=(0,0,0), pB=(0,0,1), pC=(1,3,0); path3 g=pA--pB--pC--cycle, g1=t1*g, g2=t2*g; draw(g,heavygreen); draw(g1,red); draw(g2,blue); draw(pA--t1 * pA,red+dotted,Arrow3); draw(pB--t1 * pB,red+dotted,Arrow3); draw(pC--t1 * pC,red+dotted,Arrow3); draw(pA--t2 * pA,blue+dotted,Arrow3); draw(pB--t2 * pB,blue+dotted,Arrow3); draw(pC--t2 * pC,blue+dotted,Arrow3); Ïðèìåð 13.27. Ïîî÷åðåäíîå ìàñøòàáèðîâàíèå âäîëü êîîðäèíàòíûõ îñåé. import three; size(7.5cm,0); currentprojection=obliqueX; draw(O--1.9*X,Arrow3);label("$x$",1.9*X,NW); draw(O--1.9*Y,Arrow3);label("$y$",1.9*Y,S); draw(O--2*Z,Arrow3);label("$z$",2*Z,NW); real k=3; draw(unitcylinder,blue+opacity(0.75)); draw(shift(0,-5,-3.5)*xscale3(k) *unitcylinder,brown); draw(shift(0,0,-3.5)*yscale3(k) *unitcylinder,yellow); draw(shift(0,4.5,-3.5)*zscale3(k) *unitcylinder,red); производит масштабирование на одну и ту же величину s сразу вдоль всех координатных осей, а оператор transform3 scale(real x, y, z) масштабирует один и тот же объект вдоль каждой координатной оси на разные величины x, y и z. В примере 13.28 единичный диск с помощью оператора scale3 масштабируется 5 раз, постепенно увеличиваясь в размерах и синхронно с этим изменяя свой цвет и расположение в пространстве. Пример 13.29 показывает, как, масштабируя сферу, можно превратить ее в почти плоский круг, получив в результате изображение, напоминающее планету Сатурн. Следующим преобразованием является вращение. Его реализуют два оператора. Первый transform3 rotate(real angle, triple v) поворачивает геометрический объект на угол angle (в градусах) вокруг вектора v. Второй transform3 rotate(real angle, triple u, triple v) выполняет поворот на угол angle (в градусах) вокруг оси, проходящей через точки u и v. Глава 13. Модуль three 145 Ïðèìåð 13.28. Îäíîâðåìåííîå ìàñøòàáèðîâàíèå âî âñåì êîîðäèíàòàì íà îäíó è òó æå âåëè÷èíó. import three; size(4cm,0); currentprojection=perspective(1,1,5); for(real s=1; s<=5; ++s){ transform3 ag=scale3(s); draw(ag*shift((0,0,s))*unitdisk,red+opacity(1/s)); } Ïðèìåð 13.29. Ñæàòèå ñôåðû îïåðàòîðîì scale. import three; size(7.5cm,0); currentprojection=orthographic(3,1,1); transform3 ag=scale(1.5,2.5,0.25); draw(unitsphere,blue+opacity(0.3)); draw(ag*unitsphere,heavymagenta); Ïðèìåð 13.30. Ïîâîðîò òðåóãîëüíèêà âîêðóã îñè Ox. import three; size(5.5cm,0); currentprojection=orthographic(3,1,2); pen dotteddash=linetype("0 4 4 4"), p2=.8bp+blue+dotted; transform3 r=rotate(-90,X); triple pA=(1,0,1), pB=(4,0,1), pC=(1,0,4); path3 tri=pA--pB--pC--cycle; path3 trip=r*tri; draw(tri,blue); draw(trip,heavygreen); draw(O--X,Arrow3);label("$x$",1.3*X,NW); draw(O--Y,Arrow3);label("$y$",Y,SE); draw(O--Z,Arrow3);label("$z$",1.2*Z,E); draw((-1,0,0)--(4,0,0),red+dotteddash); draw(arc((pA.x,0,0),pA,r*pA,CCW),p2,Arrow3(2.5mm)); draw(arc((pB.x,0,0),pB,r*pB,CCW),p2,Arrow3(2.5mm)); draw(arc((pC.x,0,0),pC,r*pC,CCW),p2,Arrow3(2.5mm)); На рис. примера 13.30 синий треугольник поворачивается на угол −90◦ вокруг оси абсцисс и становится зеленым. В примере 13.31 синий куб поворачивается на 180◦ вокруг оси, проходящей через точки (0, 1, 0) и (1, 1, 0), и становится желтым. Отражение, или преобразование симметрии, объекта относительно плоскости производится оператором Глава 13. Модуль three 146 Ïðèìåð 13.31. Ïîâîðîò êóáà âîêðóã íåêîîðäèíàòíîé îñè. import three; size(5.5cm,0); currentprojection=obliqueX; currentlight=(3,2,6); pen dotteddash=linetype("0 4 4 4"); triple point1=(0,1,0), point2=(1,1,0); transform3 r=rotate(180,point1,point2); draw(unitcube,royalblue); draw(r*unitcube,yellow); draw((-0.5,1,0)--(1.5,1,0),dotteddash); transform3 reflect(triple u, triple v, triple w) где u, v, w – точки, принадлежащие плоскости. Пример 13.32 демонстрирует отражение полусферы от плоскости. Ïðèìåð 13.32. Ïðåîáðàçîâàíèå ñèììåòðèè. import three; size(7.5cm,0); currentprojection=orthographic(4,4,2); currentlight=(4,2,4); triple pA=(-3,0,0), pB=(0,3,0), pC=(0,0,3); transform3 sym=reflect(pA,pB,pC); path3 tr=pA--pB--pC--cycle; draw(surface(tr),opacity(0.2)); draw(unithemisphere,blue+opacity(0.5)); draw(sym*unithemisphere,red+opacity(0.5)); triple pS=(0,0,1), pE=(0,1,0); draw(pS--sym*pS,dashed); draw(pE--sym*pE,dashed); dot((pS+sym*pS)/2); dot((pE+sym*pE)/2); Следующим преобразованием является проектирование объекта в направлении dir на плоскость, проходящую через точку O (это не обязательно начало координат) и имеющую нормаль n: transform3 planeproject(triple n, triple O=O, triple dir=n); Если нормаль не известна, ее можно вычислить с помощью процедуры triple normal(path3 p); получив в результате единичную нормаль для плоского пути p. Видимо, чтобы избавить от подобных расчетов, существует еще один вариант оператора проектирования, который дает проекцию в направлении dir на плоскость, которую определяет принадлежащий ей путь p: transform3 planeproject(path3 p, triple dir=normal(p)); Глава 13. Модуль three 147 В примере 13.33 красная кривая проектируется на все три координатные плоскости. Вначале единичный квадрат помещается на одну из координатных плоскостей с помощью трех из шести преобразований XY, YZ, ZX, YX, ZY, ZX, которые, вообще говоря, любой объект, в частности, метку, помещают на соответствующую координатную плоскость. Этот квадрат и есть тот путь, который определяет плоскость проектирования и на которую проектируется красная кривая. Естественно, как и всякое преобразование, преобразование planeproject «умножается» на эту кривую p. Ïðèìåð 13.33. Ïðîåêòèðîâàíèå íà êîîðäèíàòíûå ïëîñêîñòè. import three; size3(6cm,IgnoreAspect); currentprojection=orthographic(4,6,2); path3 p=(1,2,0)..(0,1,1)..(1,0,0)..(2,1,1)..cycle; draw(p,bp+red); draw(planeproject(XY*unitsquare3)*p,bp+brown); draw(planeproject(YZ*unitsquare3)*p,bp+heavygreen); draw(planeproject(ZX*unitsquare3)*p,bp+blue); draw(Label("$x$",2),O--2.1*X,Arrow3); draw(Label("$y$",2),O--2.2*Y,Arrow3); draw(Label("$z$",1),O--1.1*Z,Arrow3); Еще одно преобразование, которое можно назвать инвертированием, устанавливает связь между точками некоторой плоскости и точками трехмерного пространства, координаты которых вычисляются в соответствии с текущей косоугольной системой координат. Чаще всего в качестве упомянутой плоскости выступает плоскость экрана компьютера, нормаль к которой направлена к пользователю (наблюдателю). Это бывает необходимо для того, чтобы перевести экранные координаты точек в координаты точек, имитирующих изображение в текущей трехмерной проекции. На рис. примера 13.34 изображен равносторонний треугольник, вершины √которого √ в экранной, двумерной, системе координат задаются парами (0, 0), (3, 0), (1.5, 1.5 3), 1.5 3 ≈ 2.6. Проектируя вершины (синие штриховые линии) на оси трехмерной системы координат, получаем координаты вершин треугольника в этой системе координат. Такие вычисления и выполняет оператор invert. Следует обратить внимание на вывод координат двух вершин непосредственно на рис. Процедуры surface extrude(path p, triple axis=Z); surface extrude(Label L, triple axis=Z); создают поверхности в результате вытягивания пути p типа path (но не path3!) или метки L вдоль оси axis. На рисунке примера 13.35 одна окружность, сдвигаясь вдоль координатной оси Oz вычерчивает желтый цилиндр, а вторая, перемещаясь вдоль оси 0.5Y+1.7*Z, вырастает в красный цилиндр. Второй пример, 13.36, на эту тему демонстрирует, что чертить в пространстве можно не только кривыми, но и буквами, в результате чего они становятся объемными, вырастая на требуемую величину в требуемом направлении. Глава 13. Модуль three Ïðèìåð 13.34. Ñâÿçü ìåæäó ýêðàííûìè è òðåõìåðíûìè êîîðäèíàòàìè. import three; size(7cm); currentprojection=orthographic(5,5,5); triple pO=(0,0,0), pA=invert((0,0),Z,pO), pB=invert((3,0),Z,pO), pC=invert((1.5,1.5*sqrt(3)),Z,pO); draw(pA--pB--pC--cycle,red); void affichercoordonnees(triple pM,align d){ label(format("(%.3f,",pM.x)+ format("%.3f,",pM.y)+ format("%.3f)",pM.z),pM,d);} affichercoordonnees(pB,E); affichercoordonnees(pC,NNE); draw(Label("$x$",4),-4.5X--2X,Arrow3); draw(Label("$y$",2),-2.5Y--3Y,Arrow3); draw(Label("$z$",4),O--5Z,Arrow3); void montrecoord(triple P, pen stylo){ draw((P.x,0,0)--P--(0,P.y,0),stylo);} montrecoord(pB,blue+dashed); montrecoord(pC,blue+dashed); Ïðèìåð 13.35. Âûòÿãèâàíèå îêðóæíîñòåé âäîëü çàäàííûõ îñåé. import three; currentprojection=obliqueX; size(5cm,0); path g=(1,0)..(0,1)..(-1,0)..(0,-1)..cycle; surface s=extrude(g); draw(s,yellow); path g2=scale(0.5)*g; surface s2=extrude(g2,0.5Y+1.7*Z); draw(s2,red); Ïðèìåð 13.36. Ïðåâðàùåíèå ïëîñêèõ áóêâ â îáúåìíûå. import three; size(6.5cm,0); currentprojection=orthographic(-2,-2,3); currentlight=light((0,0,3),(0,-3,3)); Label L=Label("\textsc{Asymptote}",(0,0)); Label M=Label("\small is my tool", (0,0)); surface s=extrude(L,3*Z); surface t=shift((4,-6,-8))*rotate(45,X) *extrude(M,1.5Z); draw(s,blue); draw(t,red); 148 Ãëàâà 14 Ìîäóëü graph3 Модуль graph3 дает возможность изображать трехмерные системы координат и поверхности, являющиеся графиками функций. 14.1 Îñè êîîðäèíàò Все три координатные оси рисует процедура void axes3(picture pic=currentpicture, Label xlabel="", Label ylabel="", Label zlabel="", bool extend=false, triple min=(-infinity,-infinity,-infinity), triple max=(infinity,infinity,infinity), pen p=currentpen, arrowbar3 arrow=None); так что в простейшем случае они изображаются, как в примере 14.1, а в более «художественном» исполнении – как в примере 14.2. На последнем рис. следует обратить внимание на выравнивание названий осей. Для оси Ox использована конструкция dir(X-O), а для осей Oy и Oz – выражения dir(O--Y) и dir(O--Z), соответственно. Результат выполнения выравниваний одинаков: название оси располагается в направлении ее продолжения, вблизи ее конца. Вообще для направления, задаваемого вектором AB, выражение dir(A--B) обеспечивает изображение метки у точки B, а выражение dir(A-B) – у точки A. Ïðèìåð 14.1. Ïðîñòåéøåå ïðèìåíåíèå êîìàíäû axes3. import graph3; size(5.5cm,0); currentprojection=orthographic(1,1,1); axes3(min=(0,0,0),max=(1.5,1.5,1.5)); Если есть необходимость каждую ось изобразить или разметить специальным образом, то для ее рисования следует применить отдельный оператор. Например, чтобы нарисовать ось Ox, надо обратиться к процедуре 149 Глава 14. Модуль graph3 150 Ïðèìåð 14.2. Òðè îñè ñðàçó. import graph3; size(5.5cm,0); currentprojection=orthographic(1,1,1); pen mahogany=cmyk(0,0.85,0.87,0.35); axes3(xlabel=Label("$x$",dir(X-O),mahogany), ylabel=Label("$y$",dir(O--Y),mahogany), zlabel=Label("$z$",dir(O--Z),mahogany), min=(0,0,0),max=(1.5,1.5,1.5),orange, Arrow3(mahogany)); label("$O$",O,1.5*WNW,mahogany); void xaxis3(picture pic=currentpicture, Label L="", axis axis=YZZero, real xmin=-infinity, real xmax=infinity, pen p=currentpen, ticks3 ticks=NoTicks3, arrowbar3 arrow=None, bool above=false); Аналогичные процедуры yaxis3 и zaxis3 рисуют оси ординат и аппликат в трехмерном пространстве. Результат отдельного рисования координатных осей см. в примере 14.3. Ïðèìåð 14.3. Èíäèâèäóàëüíîå èçîáðàæåíèå îñåé. import graph3; size(5.5cm,0); currentprojection=orthographic(1,1,1); xaxis3("$x$",xmin=0,xmax=1.5,lightblue,Arrow3(blue)); yaxis3("$y$",ymin=0,ymax=1.5,lightred,Arrow3(red)); zaxis3("$z$",zmin=0,zmax=1.5,green, Arrow3(heavygreen)); label("$O$",O,1.5*WNW,brown); Аргумент axis в процедуре xaxis3 по умолчанию имеет значение YZZero, что означает расположение оси Ox на прямой y = 0, z = 0. Аналогичным образом для осей Oy и Oz используются значения XZZero и XYZero, соответственно. Чтобы указать другое расположение координатных осей, следует применить процедуры axis YZEquals(real y, real z, triple align=O, bool extend=false); axis XZEquals(real x, real z, triple align=O, bool extend=false); axis XYEquals(real x, real y, triple align=O, bool extend=false); Параметр ticks, как и в двумерном случае, принимает значения NoTicks3, InTicks, OutTicks и InOutTicks. Параметры xmin и xmax задавать не обязательно, Asymptote определяет их автоматически, исходя из размеров изображения. В примере 14.4 начало координат перенесено в точку (−2; −2; 0); метки на осях размещены различным образом; использованы отметки разных типов. Еще один вариант изображения осей предоставляют процедуры Глава 14. Модуль graph3 151 Ïðèìåð 14.4. Ìîäèôèêàöèè òðåõìåðíûõ îñåé. import graph3; size(5.5cm,0); currentprojection=orthographic(1,1,1); defaultpen(fontsize(10pt)); limits((-2,-2,0),(2,2,2)); xaxis3(Label("$x$", position=MidPoint, align=N),YZEquals(-2,0), InOutTicks()); yaxis3(Label("$y$", position=EndPoint, align=E),XZEquals(-2,0), OutTicks()); zaxis3("$z$",XYEquals(-2,-2), InTicks()); path3 g=(2,-2,0)--(-2,-2,0)--(-2,2,0)--(2,2,0)--cycle; draw(g,red); axis YZZero(triple align=O, bool extend=false); axis XZZero(triple align=O, bool extend=false); axis XYZero(triple align=O, bool extend=false); Необязательный параметр align применяется для изменения принятого по умолчанию выравнивания оси, черточек на ней и расположения названия оси. Впрочем, эти процедуры используются редко, так как являются частными случаями процедур YZEquals и т. д. Пример 14.5 демонстрирует задание логарифмического масштаба на оси аппликат. Ïðèìåð 14.5. Ëîãàðèôìè÷åñêèé ìàñøòàá. import graph3; size(0,200); size3(200,IgnoreAspect); currentprojection=perspective(5,2,2); scale(Linear,Linear,Log); xaxis3("$x$",0,1,OutTicks(2,2)); yaxis3("$y$",0,1,OutTicks(2,2)); zaxis3("$z$",1,30,OutTicks(beginlabel=false)); Если axis=Bounds, вместо осей координат Asymptote выводит ограничивающий рисунок параллелепипед (пример 14.6). Множитель типа XZ(), стоящий перед меткой-названием оси, определяет, в какой координатной плоскости лежит данная метка. Вообще-то bounds является процедурой вида axis Bounds(int type=Both, int type2=Both, triple align=O, bool extend=false); Каждый из параметров type и type2 принимает одно из трех значений: Min, Max или Both. Эти параметры определяют, какие из четырех возможных ребер трехмерного ограничивающего параллелепипеда будут нарисованы. На рис. примера 14.7 ребра красного цвета, параллельные оси Ox, занумерованы. Нетрудно заметить, что эти ребра определяются граничными Глава 14. Модуль graph3 152 Ïðèìåð 14.6. Îãðàíè÷èâàþùèé ïàðàëëåëåïèïåä. import graph3; size(5.5cm,0); currentprojection=orthographic(4,6,4); draw((1,0,0)..(0.5,0.5,0.8)..(0,1,1)); xaxis3(XZ()*"$x$",Bounds,blue,InTicks); yaxis3(YZ()*"$y$",Bounds,blue, InTicks(beginlabel=false,Label,2,2)); zaxis3(XZ()*"$z$",Bounds,blue,InTicks); значениями координат x и z параллелепипеда. Поэтому ребру 1 отвечает набор параметров (Min,Min), то есть x → min, z → min. (Min,Both) означает, что x → min, а z берется и min, и max. Поэтому (Min,Both) выбирает ребра 1 и 2. Отметим также, что по умолчанию (Min,Both)=(Min). Ребра 2 и 4 выбираются сочетанием (Both,Max). Еще учтем, что (Both,Both)=(Both)=() оставляет все четыре ребра. В результате получаем таблицу 14.1. Таблица 14.1. x y Ребра Min Min 1 Min Max 2 Max Min 3 Max Max 4 Min Both 1, 2 Max Both 3, 4 Both Min 1, 3 Both Max 2, 4 Both Both 1, 2, 3, 4 Ïðèìåð 14.7. Ê îïðåäåëåíèþ ïàðàìåòðîâ bounds. import graph3; size(5.5cm); currentprojection=orthographic(4,6,3); limits((0,-2,0),(2,2,2)); xaxis3("$x$",Bounds(),red); yaxis3("$y$",Bounds(),blue); zaxis3("$z$",Bounds(),green); dot(O,L=Label("$O$"),NE); label("2",(1,-2,2),NW,red); label("1",(1,-2,0),NW,red); label("4",(1,2,2),NW,red); label("3",(1,2,0),NW,red); Рисунок 14.8 демонстрирует выбор ребер ограничивающего бокса. Как уже отмечалось, в трехмерном случае для рисования отметок используются опции NoTicks3, InTicks, OutTicks и InOutTicks. Они задают направление черточек для осей вида Bounds; другие типы осей наследуют направления, которые соответствуют направлениям Bounds(Min,Min). Как и в двумерном случае, можно разметить и «кривую» ось, заданную в виде некоторого пути (пример 14.9). Глава 14. Модуль graph3 153 Ïðèìåð 14.8. Âûáîð ðåáåð îãðàíè÷èâàþùåãî áîêñà. import graph3; size(5.5cm); limits((0,0,0),(10,10,6)); currentprojection=perspective(camera=(20,16,7)); xaxis3(Label("$x$",MidPoint,align=Y-Z), Bounds(Both,Min),OutTicks(endlabel=false),p=blue); yaxis3(Label("$y$",MidPoint,align=X-Z), Bounds(Both,Min),OutTicks(),p=red); zaxis3(Label("$z$",MidPoint,align=X-Y), Bounds(Min,Min),InTicks(),p=1bp+.5green); Ïðèìåð 14.9. Êîîðäèíàòíàÿ ðàçìåòêà êðèâîé. import graph3; size(5.5cm); path3 g= X..2Y..-X..-2Y..cycle;; currentprojection=perspective(10,10,10); axis(Label("C",position=0,align=15X),g, InTicks(endlabel=false,8,end=false), ticklocate(0,360,new real(real v) {path3 h=O--max(abs(max(g)),abs(min(g)))*dir(90,v); return intersect(g,h)[0];}, new triple(real t) {return cross(dir(g,t),Z);})); 14.2 Êîîðäèíàòíûå ñåòêè è ìîäóëü grid3 Если к работе подключить модуль grid3, рисунок можно снабдить трехмерной координатной сеткой. Построение такой сетки выполняет процедура grid3: void grid3(picture pic=currentpicture, grid3routinetype gridroutine=XYZgrid(real pos=Relative(0)), int N=0, int n=0, real Step=0, real step=0, bool begin=true, blbool end=true, pen pGrid=grey, pen pgrid=lightgrey, bool above=false); Большинство параметров нам известно из двумерного аналога, пояснения требует только использование процедуры gridroutine. Она имеет следующие ипостаси: • XYgrid создает на плоскости xOy координатную сетку, линии которой параллельны Oy; • YXgrid создает на плоскости xOy сетку, линии которой параллельны Ox; и т. д. • XYXgrid создает пару сеток: XYgrid и YXgrid; • YXYgrid делает то же самое; • ZXZgrid создает пару сеток: ZXgrid и XZgrid; и т. д.; • YX_YZgrid создает сетки YXgrid и YZgrid; Глава 14. Модуль graph3 154 • XY_XZgrid создает сетки XYgrid и XZgrid; • ZX_ZYgrid создает сетки ZXgrid и ZYgrid; • XYZgrid создает XYXgrid, ZYZgrid и XZXgrid. В первом примере 14.10 на эту тему показано, как строятся координатные сетки в плоскостях xOy и xOz в направлении осей Oy и Oz, соответственно. Ïðèìåð 14.10. Êîîðäèíàòíàÿ ñåòêà â xOy â íàïðàâëåíèè Oy è â xOz â íàïðàâëåíèè Oz. import grid3; size(7.5cm); limits((0,0,0),(5,5,3)); currentprojection= perspective(camera=(20,16,7)); xaxis3(Label("$x$",MidPoint,align=Y-Z), Bounds(Both,Min), OutTicks(endlabel=false),p=blue); yaxis3(Label("$y$",MidPoint,align=X-Z), Bounds(Both,Min),OutTicks(),p=red); zaxis3(Label("$z$",MidPoint,align=X-Y), Bounds(Both,Min), InTicks(),p=1bp+.5green); grid3(XYgrid); grid3(XZgrid); Того же эффекта можно было добиться, использовав вместо двух операторов grid3 один: grid3(XY_XZgrid). В следующем примере 14.11 на плоскости xOy реализуется направление линий сетки, перпендикулярное предыдущему. Ïðèìåð 14.11. Êîîðäèíàòíàÿ ñåòêà â ïëîñêîñòÿõ xOy è yOz. import grid3; size(7.5cm); limits((0,0,0),(5,5,3)); currentprojection= perspective(camera=(20,16,7)); xaxis3(Label("$x$",MidPoint,align=Y-Z), Bounds(Both,Min), OutTicks(endlabel=false),p=blue); yaxis3(Label("$y$",MidPoint,align=X-Z), Bounds(Both,Min),OutTicks(),p=red); zaxis3(Label("$z$",MidPoint,align=X-Y), Bounds(Both,Min), InTicks(),p=1bp+.5green); grid3(YXgrid); grid3(YZgrid); Построение двойной координатной сетки в плоскости xOy показано в примере 14.12 Рисунок примера 14.13 демонстрирует создание двойных сеток на трех координатных плоскостях. Естественно, что допускается использование логарифмического масштабирования (пример 14.14). Глава 14. Модуль graph3 Ïðèìåð 14.12. Äâîéíàÿ êîîðäèíàòíàÿ ñåòêà. import grid3; size(7.5cm); limits((0,0,0),(5,5,3)); currentprojection= perspective(camera=(20,16,7)); xaxis3(Label("$x$",MidPoint,align=Y-Z), Bounds(Both,Min), OutTicks(endlabel=false),p=blue); yaxis3(Label("$y$",MidPoint,align=X-Z), Bounds(Both,Min),OutTicks(),p=red); zaxis3(Label("$z$",MidPoint,align=X-Y), Bounds(Both,Min), InTicks(),p=1bp+.5green); grid3(XYXgrid); Ïðèìåð 14.13. Òðè äâîéíûå êîîðäèíàòíûå ñåòêè. import grid3; size(7.5cm); limits((0,0,0),(10,10,6)); currentprojection= perspective(camera=(20,16,7)); grid3(XYZgrid,Step=2); xaxis3(Label("$x$",MidPoint,align=Y-Z), Bounds(Both,Min), OutTicks(endlabel=false),p=blue); yaxis3(Label("$y$",MidPoint,align=X-Z), Bounds(Both,Min),OutTicks(),p=red); zaxis3(Label("$z$",MidPoint,align=X-Y), Bounds(Both,Min), InTicks(),p=1bp+.5green); Ïðèìåð 14.14. Ëîãàðèôìè÷åñêîå ìàñøòàáèðîâàíèå. import grid3; size(6cm); currentprojection=orthographic(1,1,1); scale(Log,Linear,Log); limits((1,0,1),(300,3,300)); grid3(XYZgrid); xaxis3("$x$",OutTicks()); yaxis3("$y$",OutTicks(NoZero)); zaxis3("$z$",InOutTicks()); 155 Глава 14. Модуль graph3 156 Параметр Relative(real x=0) определяет положение сетки по отношению к перпендикулярной к ней координатной оси. Значениям Relative(1), Relative(0.5) и Relative(0) отвечают, соответственно, pos=top, pos=middle и pos=bottom. Примеры 14.15 и 14.16 показывают, как влияет этот параметр на изменение положения сетки, связанной с плоскостью xOz. Следует также обратить внимание на совместное использование координатной сетки и традиционных координатных осей. Ïðèìåð 14.15. Ïðèíÿòîå ïî óìîë÷àíèþ çíà÷åíèÿ ïàðàìåòðà Relative. import graph3; import grid3; size(6cm); currentprojection=obliqueX; limits((0,-2,0),(4,6,4)); grid3(XYXgrid,Step=1,step=0); grid3(ZYZgrid,Step=1,step=0); grid3(XZXgrid,Step=1,step=0); axes3("$x$","$y$","$z$", red+linewidth(1pt),Arrow3(3mm)); Ïðèìåð 14.16. Relative(0.25). import graph3; import grid3; size(6cm); currentprojection=obliqueX; limits((0,-2,0),(4,6,4)); grid3(XYXgrid,Step=1,step=0); grid3(ZYZgrid,Step=1,step=0); grid3(XZXgrid(Relative(0.25)),Step=1,step=0); axes3("$x$","$y$","$z$", red+linewidth(1pt),Arrow3(3mm)); Следующий пример 14.16 дополняет предыдущий добавлением отметок на координатных осях. До сих пор координатная сетка присоединялась к рисунку с помощью процедуры grid3, но и в процедурах, изображающих координатные оси, можно указать на необходимость вычерчивания сетки. Это продемонстрировано в примере 14.18. 14.3 Ãðàôèêè ôóíêöèé êàê ïîâåðõíîñòè 14.3.1 Äåêàðòîâà ñèñòåìà êîîðäèíàò Поверхности могут задаваться аналитически как графики функций. В Asymptote такие поверхности создаются с помощью процедур surface surface(real f(pair z), pair a, pair b, int nx=nmesh, int ny=nx, bool cond(pair z)=null); surface surface(real f(pair z), pair a, pair b, int nx=nmesh, int ny=nx, splinetype xsplinetype, splinetype ysplinetype=xsplinetype, bool cond(pair z)=null); Глава 14. Модуль graph3 157 Ïðèìåð 14.17. Ïîëíîå êîîðäèíàòíîå ñíàðÿæåíèå. import graph3; import grid3; size(6cm); currentprojection=obliqueX; limits((0,-2,0),(4,6,4)); grid3(XYXgrid,Step=1,step=0); grid3(ZYZgrid,Step=1,step=0); grid3(XZXgrid,Step=1,step=0); xaxis3(Label("$x$",position=EndPoint,align=W), 0,4.5,red+linewidth(1pt),OutTicks(NoZero, beginlabel=false,endlabel=false,Step=2,step=1, end=false),Arrow3(3mm)); yaxis3(Label("$y$",position=EndPoint,align=N), -2,6.5,red+linewidth(1pt),OutTicks(NoZero, beginlabel=true,endlabel=false,Step=2,step=1, end=false),Arrow3(3mm)); zaxis3(Label("$z$",position=EndPoint,align=E), -2,4.5,red+linewidth(1pt),OutTicks(NoZero, beginlabel=false,endlabel=false,Step=2,step=1,end=false),Arrow3(3mm)); dot("$O$",O,SE); Ïðèìåð 14.18. Çàäàíèå ñåòîê â ïðîöåäóðàõ äëÿ îñåé. import grid3; size(6cm); currentprojection=orthographic(0.25,1,0.25); limits((-2,-2,0),(0,2,2)); real Step=1, step=0.5; xaxis3(Label("$x$",position=EndPoint,align=Z), YZEquals(-2,0),InOutTicks(Label(align=0.5*(Z-Y)), Step=Step,step=step,gridroutine=XYgrid,pGrid=red, pgrid=0.5red)); yaxis3(Label("$y$",position=EndPoint,align=Z), XZEquals(-2,0),InOutTicks(Label( align=-0.5*(X-Z)),Step=Step,step=step, gridroutine=YXgrid,pGrid=red,pgrid=0.5red)); zaxis3("$z$",XYEquals(-1,0),OutTicks(Label( align=-0.5*(X+Y)))); Поверхность определяется функцией f(z), областью задания которой является прямоугольник с противоположными вершинами a и b. Параметры nx и ny регулируют размеры ячеек Безье, формирующих поверхность. Чем больше значения этих параметров, тем качественнее изображение. По умолчанию nx=ny=nmesh=10. Для сглаживания сплайнами надо указать Spline. В примере 14.19 поверхность задается в виде функции z = −0,3(x2 + y 2 ), а количество ячеек Безье взято принятым по умолчанию. Из рис. видно, что получилась довольно грубая аппроксимация поверхности. Увеличив число ячеек Безье, получим лучшую аппроксимацию, пример 14.20. Еще более высокое качество обеспечивает применение сплайнов, реализованное в примере 14.21. Размеры pdf-файлов для изображений в этих трех примерах увеличивались следующим образом: 25, 50, 76 Кб. Условие cond, используемое в процедурах surface, служит для задания области определения функции. В предыдущих примерах выбиралась стандартная форма области – прямоугольник. Чтобы построить поверхность не над прямоугольником, а, например, над кругом, Глава 14. Модуль graph3 Ïðèìåð 14.19. Èçîáðàæåíèå ïîâåðõíîñòè z = −0.3(x2 + y2 ) ïðè 158 nx=ny=10. import graph3; currentprojection=orthographic(2,3,4); currentlight=light(1,0,7); size(6.5cm,0); real f(pair z){ return -0.3(z.x^2+z.y^2); } surface s=surface(f,(-2,-2),(2,2)); draw(s,royalblue); Ïðèìåð 14.20. Èçîáðàæåíèå ïîâåðõíîñòè z = −0.3(x2 + y2 ) ïðè nx=ny=25. import graph3; currentprojection=orthographic(2,3,4); currentlight=light(1,0,7); size(6.5cm,0); real f(pair z){ return -0.3(z.x^2+z.y^2); } surface s=surface(f,(-2,-2),(2,2), nx=25,ny=25); draw(s,royalblue); Ïðèìåð 14.21. Ïðèìåíåíèå ñïëàéíîâ. import graph3; currentprojection=orthographic(2,3,4); currentlight=light(1,0,7); size(6.5cm,0); real f(pair z){ return -0.3(z.x^2+z.y^2); } surface s=surface(f,(-2,-2),(2,2),Spline); draw(s,royalblue); необходимо написать соответствующую булевскую функцию cond. Это и выполнено в примере 14.22. Правда, при малых значениях nx и ny край поверхности представляет собой весьма зубчатую линию. Поэтому пришлось взять nx=ny=400, чтобы зубчатость уменьшилась, но полностью устранить это явление не удалось, так как дальнейшее увеличение nx и ny привело к нехватке компьютерной памяти. При этом объем файла изображения составил 108 Кб. К более качественным результатам приводит использование подходящей системы координат (цилиндрической или сферической), о которых речь пойдет впереди (пример 14.25). Глава 14. Модуль graph3 Ïðèìåð 14.22. Èñïîëüçîâàíèå óñëîâèÿ 159 cond. import graph3; currentprojection=orthographic(2,3,4); currentlight=light(1,0,7); size(6.5cm,0); real f(pair z){ return -(z.x^2+z.y^2);} surface s=surface(f,(-2,-2),(2,2),400,Spline, cond=new bool(pair z){ return z.x^2+z.y^2<=1 ? true : false;}); draw(s,heavygreen); В некоторых вариантах процедуры draw можно выполнить нанесение сетки на поверхность и задать освещение. Так, в примере 14.23 освещение отключено, а эффект трехмерности достигается нанесением на поверхность сетки. В примере же 14.24 используется освещение по Ïðèìåð 14.23. Ñåòêà è îòñóòñòâèå îñâåùåíèÿ. import graph3; size(7cm,0); currentprojection=perspective(2,2,2); real a=2; real f(pair z) {return exp(-abs(z)^2)+0.25;} surface s=surface(f,(-a,-a),(a,a),nx=25); path3 pl=plane(2*(a,0,0),2*(0,a,0),(-a,-a,0)); draw(surface(pl),gray); draw(s,lightgray, meshpen=black+thick(), nolight); xaxis3("",0,a+0.5,red,Arrow3); yaxis3("",0,a+0.5,red,Arrow3); zaxis3("",0,1.75,red,Arrow3); умолчанию. В результате на поверхности появляется блик от источника света. В обоих примерах аргументом функции является комплексная величина z. 14.3.2 Ïàðàìåòðè÷åñêîå çàäàíèå ïîâåðõíîñòè Следующие варианты процедур surface позволяют создавать графики параметрически заданных поверхностей: surface surface(triple f(pair z), pair a, pair b, int nu=nmesh, int nv=nu, bool cond(pair z)=null); surface surface(triple f(pair z), pair a, pair b, int nu=nmesh, int nv=nu, splinetype[] usplinetype, splinetype[] vsplinetype=Spline, bool cond(pair z)=null); surface surface(triple f(pair z), real[] u, real[] v, splinetype[] usplinetype, Глава 14. Модуль graph3 160 Ïðèìåð 14.24. Ñåòêà è îñâåùåíèå ïî óìîë÷àíèþ. import graph3; size(7cm,0); currentprojection=perspective(2,2,2); real a=2; real f(pair z) {return exp(-abs(z)^2)+0.25;} surface s=surface(f,(-a,-a),(a,a),nx=25); path3 pl=plane(2*(a,0,0),2*(0,a,0),(-a,-a,0)); draw(surface(pl),gray); draw(s,lightyellow, meshpen=brown+thick()); xaxis3("",0,a+0.5,red,Arrow3); yaxis3("",0,a+0.5,red,Arrow3); zaxis3("",0,1.75,red,Arrow3); splinetype[] vsplinetype=Spline, bool cond(pair z)=null); Теперь функция f зависит от пары z, компоненты которой считаются параметрами поверхности. Значением функции является тройка (вектор) декартовых координат точки в пространстве. Нарисуем таким образом однополостный гиперболоид, x2 y 2 z 2 + 2 − 2 = 1, a2 b c который можно определить в виде двупараметрической поверхности x = a ch u cos v, y = b ch u sin v, z = c cos v, −∞ < u < ∞, −π < v ≤ π. Результат приведен в примере 14.25. Ïðèìåð 14.25. Îäíîïîëîñòíûé ãèïåðáîëîèä êàê äâóïàðàìåòðè÷åñêàÿ ïîâåðõíîñòü. import graph3; currentprojection=perspective(2,2,2.8); size(6cm,0); real a=1.5; triple f(pair z){ return (cosh(z.x)*cos(z.y),cosh(z.x)*sin(z.y), sinh(z.x)); } surface s=surface(f,(-a,-pi),(a,pi),Spline); draw(s,lightred,meshpen=blue+thick()); Глава 14. Модуль graph3 14.3.3 161 Öèëèíäðè÷åñêàÿ ñèñòåìà êîîðäèíàò Если поверхность задана в одной из традиционных систем координат, то ее уравнение следует подходящим образом преобразовать. Изобразим, например, комбинацию из двух поверхностей: эллиптического параболоида z = 6 − x2 − y 2 и конуса x2 + y 2 − z 2 = 0, для чего перейдем к цилиндрической системе координат по формулам x = ρ cos ϕ, y = ρ sin ϕ, z = z. Параболоид в этой системе координат запишется как z = 6 − ρ2 , а конус – как z = ρ. Поскольку выбранные поверхности пересекаются при ρ = 2, то пределы изменения координат будут такими: 0 ≤ ϕ < 2π, 0 ≤ ρ ≤ 2. Выбирая для программирования в качестве параметров ϕ и ρ, получим искомое изображение, представленное на рис. примера 14.26. Ïðèìåð 14.26. Ïîâåðõíîñòü â öèëèíäðè÷åñêîé ñèñòåìå êîîðäèíàò. import graph3; currentprojection=orthographic(3,1,1); currentlight=Viewport; size(4cm,0); triple f1(pair z) {return (z.x*cos(z.y),z.x*sin(z.y), 6-z.x^2);} surface s1=surface(f1,(0,0),(2,2pi),Spline); draw(s1,yellow); triple f2(pair z) {return (z.x*cos(z.y),z.x*sin(z.y),z.x);} surface s2=surface(f2,(0,0),(2,2pi),Spline); draw(s2,green); 14.3.4 Ñôåðè÷åñêàÿ ñèñòåìà êîîðäèíàò Перейдем теперь к сферической системе координат x = ρ cos ϕ sin θ, y = ρ sin ϕ sin θ, z = ρ cos θ и построим в ней поверхность, заданную уравнением (x2 + y 2 + z 2 )2 = 3xyz. В сферической системе координат уравнение примет вид ρ= 3 sin 2ϕ sin 2θ sin θ. 4 Эту функцию ρ = ρ(ϕ, θ) запрограммируем отдельно и подставим в уравнения для координат x, y и z, так что в результате останется требуемых два параметра: ϕ и θ. Рис. примера 14.27 демонстрирует полученный результат. 14.3.5 Ïðîçðà÷íîñòü è çà÷åð÷èâàíèå Использование полупрозрачных поверхностей позволяет визуализировать детали их пересечений и невидимые части. Таким образом получено изображение пересечение цилиндра с полусферой в примере 14.28. Аналогичным образом выполняется построение поверхностей с самопересечением. Так, в примере 14.29 демонстрируется изображение полупрозрачной бутылки Клейна. Довольно эффективным средством рисования поверхностей является зачерчивание их отрезками прямых или дугами кривых. Изобразим, например, поверхность образованную параболическим цилиндром y = x2 , который ограничивают плоскости z = 0 и z = 3−y, см. пример Глава 14. Модуль graph3 162 Ïðèìåð 14.27. Ïîâåðõíîñòü â ñôåðè÷åñêîé ñèñòåìå êîîðäèíàò. import graph3; currentprojection=orthographic(3,3,3); currentlight=Viewport; size(6cm,0); real r(pair z){ return (3*sin(2*z.x)*sin(2*z.y)*sin(z.y)/4);} triple f(pair z) {return (r(z)*cos(z.x)*sin(z.y),r(z)*sin(z.x)*sin(z.y), r(z)*cos(z.y));} surface s=surface(f,(0,0),(2pi,pi),Spline); draw(s,purple); Ïðèìåð 14.28. Ïåðåñå÷åíèå ïîëóïðîçðà÷íîé ïîëóñôåðû ñ öèëèíäðîì. import graph3; currentprojection=orthographic(3,-3,3); currentlight=Viewport; size(6cm,0); draw(scale3(1.1)*unithemisphere,green+opacity(0.7)); triple f(pair z) {return (0.5*(1+cos(z.x)),0.5*sin(z.x),z.y);} surface s=surface(f,(0,0),(2pi,1.3),50,Spline); draw(s,y); draw(s,Yellow); 14.30. Чтобы нарисовать требуемую часть параболического цилиндра, заметим, что она образуется при движении вертикального отрезка с концами A(x, x2 , 0) и B(x, x2 , 3 − x2 ) вдоль направляющей y = x2 , z = 0. Таким образом, чтобы получить все точки такого отрезка, надо ввести параметр t ∈ [0; 1], и тогда отрезок представится тройкой (x, x2 , t(3 − x2 )). Движение отрезка обеспечивается изменением переменной x, а перемещение точки вдоль отрезка – изменением параметра t. В процедуре f параметр t обозначен z.x. Верхняя часть поверхности реализуется натягиванием плоской поверхности на контур так, как это уже рассматривалось ранее. Следует обратить внимание на то, как контуры поверхности получаются из процедуры f, определяющей поверхность, с помощью процедур f0 и f1. Более сложный пример зачерчивания поверхности, заданной графиком функции, приведен в Приложении B. Другой пример для поверхности, определяемой сплайнами, представлен в Приложении C. Если поверхности заданы точками в трехмерном пространстве, их изображения создаются специальными модификациями процедуры surface для этого случая: surface surface(real[][] f, pair a, pair b, bool[][] cond={}); surface surface(real[][] f, pair a, pair b, splinetype xsplinetype, splinetype ysplinetype=xsplinetype, bool[][] cond={}); surface surface(real[][] f, real[] x, real[] y, splinetype xsplinetype=null, splinetype ysplinetype=xsplinetype, bool[][] cond={}); Глава 14. Модуль graph3 163 Ïðèìåð 14.29. Áóòûëêà Êëåéíà. settings.render=4; import graph3; size(5cm,0); currentprojection=orthographic((2,2,2)); currentlight=(3,2,1); path center_path=(0,0)---(0,3)..(1.5,3)..(0,0.3) ---(0,0); real bottomradius=0.6; real topradius=0.1; path radius_graph=(0,bottomradius) {up}::(0.3,1.2*bottomradius)---(0.6,1.2*bottomradius) ::(1.2,topradius)---(2,topradius) ::{up}(3,bottomradius); radius_graph=xscale(1/3)*radius_graph; radius_graph=(shift(-1,0)*radius_graph) &radius_graph&(shift(1,0)*radius_graph); real radius(real t){ return point(radius_graph, times(radius_graph,t)[0]).y;} triple F(pair w){ real t=w.x % 2.0; bool reverse=(t>=1.0); t %= 1.0; real relt=reltime(center_path,t); real theta=w.y; triple center=YZplane(point(center_path,relt)); pair tangent=dir(center_path,relt); if (reverse) tangent *= -1; triple normal=X; triple binormal=cross(YZplane(tangent),normal); triple v=normal*cos(theta)+binormal*sin(theta); return center+radius(t)*v;} surface kleinbottle=surface(F,(0.5,0),(1.5,2pi), nu=32,nv=16,Spline); draw(kleinbottle,blue+0.5*white+opacity(0.7)); surface surface(triple[][] f, bool[][] cond={}); С использованием процедур можно познакомиться в прилагаемых к дистрибутиву системы Asymptote примерах BezierSurface.asy и teapot.asy. Следует иметь в виду, что создание поверхностей этим способом требует определенных знаний в области мозаик Безье в пространстве и понимания используемых в Asymptote алгоритмов. 14.3.6 Ïðîåöèðîâàíèå, ïðîöåäóðà lift В некоторых случаях для специального вида изображений может быть полезна процедура guide3[][] lift(real f(real x, real y), guide[][] g, interpolate3 join=operator --); которая проецирует (поднимает) двумерные кривые g, расположенные в плоскости xOy, на поверхность, которая задается функцией f. В примере 14.31 пурпурный отрезок и зеленый прямоугольник на плоскости xOy проецируются на поверхность в соответствующие фигуры. В следующем примере 14.32 показано, как использовать процедуру lift для триангуляции поверхности (эллиптического параболоида). Глава 14. Модуль graph3 164 Ïðèìåð 14.30. Çà÷åð÷èâàíèå ïîâåðõíîñòè îòðåçêîì. import graph3; size(7cm,0); currentprojection=orthographic((10,6,5)); real a=sqrt(3), b=1, dy=.5; triple f(pair p){ real x=p.x; real y=p.x^2; real z=p.y*(a^2-p.x^2); return (x,y,z);} triple f0(real t) {return f((t,0));} triple f1(real t) {return f((t,1));} surface s=surface(f,(-a,0),(a,1),100); path3 p0=graph(f0,-a,a,operator ..), p1=graph(f1,-a,a,operator ..); draw(s,yellow+opacity(.6)); draw(p0--cycle^^p1^^(0,a^2,0)); draw(surface(p1--cycle),yellow+opacity(.6)); draw((1,1,0)--(1,1,2),white+bp); limits((-.5,-.5,-.1),(a,a^2+.5,a^2+.5)); axes3("$x$","$y$","$z$",brown,Arrow3); label("$A$",(1,1,0),SSW); label("$B$",(1,1,2),NE); draw((-0.6,0.7,2.3)--(-1,0.7,2.7)); label("$z=3-y$",(-1,0.7,2.7),N); draw((0.5,0.25,1.7)--(0.3,-0.3,1.8)); label("$y=x^2$",(0.3,-0.3,1.8),NNW); Ïðèìåð 14.31. Ïðîåêòèðîâàíèå íà ïîâåðõíîñòü. import graph3; size(7cm,0); size3(7cm,IgnoreAspect); currentprojection=orthographic(-25,-25,600); limits((0,0,0),(10,10,300)); xaxis3(OutTicks(Step=2)); yaxis3(OutTicks(Step=2)); zaxis3(Bounds(Min,Max), InTicks(Step=100,Label(align=Y))); real f(pair z){ return 2z.x^2-z.x+z.y^2;} draw(surface(f,(0,0),(10,10),nx=10,Spline), lightgray,meshpen=black+0.2bp,nolight); guide[][] tabgui={{(6,1)--(9,1)--(9,4) --(6,4)--cycle},{(1,6)--(1,8)}}; draw(lift(f,tabgui),1bp+red); path[] p1=tabgui[0], p2=tabgui[1]; draw(path3(p1),1bp+.8green); draw(path3(p2),1bp+.8purple); В сочетании с уже рассмотренной выше процедурой contour процедура lift позволяет изобразить на поверхности линии ее сечения плоскостями, параллельными плоскости xOy, см. пример 14.33. Глава 14. Модуль graph3 165 Ïðèìåð 14.32. Òðèàíãóëÿöèÿ ïîâåðõíîñòè. import graph3; size(6cm,0); int np=100; pair[] points; real r() {return 1.2 * (rand()/randMax * 2-1);} for(int i=0; i < np; ++i) points.push((r(),r())); int[][] trn=triangulate(points); guide[][] mong; for(int i=0; i < trn.length; ++i){ guide[] gg={points[trn[i][0]]-points[trn[i][1]]--points[trn[i][2]]--cycle}; mong.push(gg);} real f(pair z){ return z.x^2+z.y^2;} draw( lift(f,mong),deepgreen+0.3bp); Ïðèìåð 14.33. Ëèíèè ñå÷åíèÿ ïîâåðõíîñòè. import graph3; import contour; size(6cm,0); currentprojection=perspective(2,2,2); limits((0,0,0),(1.5,1.5,2)); int n=3; real a=1; real f(pair z) {return ((z^n).x)/a^(n-1);} real[] lignesniveaux={-1.5,-1,-0.5,0,0.5,1,1.5}; draw(surface(f,(-a,-a),(a,a),nx=10,Spline),white, meshpen=.8bp+red,nolight); draw(lift(f,contour(f,(-a,-a),(a,a),lignesniveaux)), 1bp+blue); 14.3.7 Èçîáðàæåíèå âåêòîðíûõ ïîëåé Векторное поле, нарисованное nu× nv стрелками на поверхности, расположенной над прямоугольником box(a,b), принадлежащим плоскости xOy, и определяемой параметрически заданной функцией f, может быть получено с помощью процедуры, picture vectorfield(path3 vector(pair v), triple f(pair z), pair a, pair b, int nu=nmesh, int nv=nu, bool truesize=false, real maxlength=truesize ? 0 : maxlength(f,a,b,nu,nv), bool cond(pair z)=null, pen p=currentpen, arrowbar3 arrow=Arrow3, margin3 margin=PenMargin3); В примере 14.34 построено векторное поле градиента эллиптического параболоида как на самой поверхности, так и в виде проекции поля на плоскость xOy. Глава 14. Модуль graph3 166 Ïðèìåð 14.34. Èçîáðàæåíèå òðåõìåðíîãî âåêòîðíîãî ïîëÿ. import graph3; size(7cm,0); currentprojection=orthographic(1,-2,1); currentlight=(1,-1,0.5); real f(pair z){return abs(z)^2;} path3 gradient(pair z){ static real dx=sqrtEpsilon, dy=dx; return O--((f(z+dx)-f(z-dx))/2dx, (f(z+I*dy)-f(z-I*dy))/2dy,0);} pair a=(-1,-1); pair b=(1,1); draw(surface(f,a,b,Spline),gray+opacity(0.5)); triple F1(pair z) {return (z.x,z.y,f(z));} add(vectorfield(gradient,F1,a,b,red)); triple F2(pair z) {return (z.x,z.y,0);} add(vectorfield(gradient,F2,a,b,blue)); axes3("$x$","$y$","$z$",brown,Arrow3); 14.3.8 Ðàñêðàøèâàíèå ñ ïîìîùüþ ïàëèòð В трехмерном пространстве есть возможность применить процедуры модуля palette для раскрашивания поверхностей. Часто при этом используют функцию Gradient, создающую палитру перьев, и функцию map(triple direction), определяющую направление, в котором будет происходить смена цветов палитры при раскрашивании поверхности. На рис. примера 14.35 в качестве такого направления выбрана ось аппликат (указанием zpart). Результат не требует комментариев. Ïðèìåð 14.35. Ïðèìåíåíèå ôóíêöèé Gradient è map äëÿ ðàñêðàøèâàíèÿ ïîâåðõíîñòè. import graph3; import palette; size(4cm,0); currentprojection=orthographic((3,3,4.5)); triple f(pair w){ return (w.x*sin(w.y),w.x*cos(w.y),w.y/3);} limits((-.5,-.5,-.1),(5,5,5)); surface s=surface(f,(-1,0),(1,12),30,Spline); s.colors(palette(s.map(zpart), Gradient(red,green,blue))); draw(s); Вместо непрерывного раскрашивания с помощью функции Gradient можно выполнить дискретное, задав для этой цели массив перьев pen[] pens=mean(palette(s.map(triple direction),pen[] palette)); При этом можно использовать и предопределенные палитры. Так, в примерах 14.36 и 14.37 использована палитра Rainbow. Глава 14. Модуль graph3 Ïðèìåð 14.36. Ïðèìåíåíèå ïàëèòðû 167 Rainbow â íàïðàâëåíèè îñè àïïëèêàò. import graph3; import palette; currentprojection=obliqueX; size(6cm,0); real f(pair z) {return -z.x^2-z.y^2;} surface s=surface(f,(-1,-1),(1,1),Spline); pen[] pens=mean(palette(s.map(zpart), Rainbow())); draw(s,pens); Ïðèìåð 14.37. Ïðèìåíåíèå ïàëèòðû Rainbow â íàïðàâëåíèè îñè àáñöèññ. import graph3; import palette; currentprojection=obliqueX; size(6cm,0); real f(pair z) {return -z.x^2-z.y^2;} surface s=surface(f,(-1,-1),(1,1),Spline); pen[] pens=mean(palette(s.map(xpart), Rainbow())); draw(s,pens); В следующем примере 14.37 создается и раскрашивается не только сама поверхность, но и ее проекция на плоскость xOy. Ïðèìåð 14.38. Ðàñêðàøèâàíèå ïîâåðõíîñòè è åå ïðîåêöèè íà ïëîñêîñòü. import graph3; import palette; currentprojection=obliqueX; currentlight=light((1,1,3),(3,3,0)); size(6cm,0); real f(pair z) {return z.x^2+z.y^2;} surface s=surface(f,(-1,-1),(1,1),nx=50); pen[] pens=mean(palette(s.map(zpart), Gradient(red,yellow,brown))); draw(s,pens); path3 pl=plane(X,Y,O); draw(planeproject(pl)*s,pens,nolight); Глава 14. Модуль graph3 14.3.9 168 Ðåëüåôíûå íàäïèñè íà ïîâåðõíîñòè Существует модификация процедуры surface, позволяющая делать надписи на поверхности вдоль видимых или невидимых линий сетки: surface surface(Label L, surface s, real uoffset, real voffset, real height=0, bool bottom=true, bool top=true); Размещение надписи на поверхности регулируется параметрами uoffset и voffset. Параметр height задает высоту букв, делая текст надписи рельефным. На рис. примера 14.39 надпись начинается с первой вертикальной линии сетки, которой соответствует uoffset=1, и со второй горизонтальной линии (voffset=2). Надпись рельефна, так как параметр height=0.1. Ïðèìåð 14.39. Ðåëüåôíàÿ íàäïèñü íà ïîâåðõíîñòè. import graph3; size(7.5cm,0); currentprojection=orthographic(-0.3,-36,34); currentlight=(0,0,3); real f(pair z){return 0.4(z.x^2-z.y^2);} surface s=surface(f,(-1,-1),(1,1),nx=10); draw(s,blue, meshpen=black+thick()); draw(surface(xscale(3)*scale(0.25)* "$F(x,y,z)=0$",s,uoffset=1,voffset=2, height=0.1),yellow); Регулируя длину надписи с помощью преобразования xscale и т. п., поворачивая поверхность и изменяя количество линий сетки на ней, можно добиться требуемого расположения надписи и затем убрать с поверхности сетку, «закомментировав» ее (если в ней нет необходимости). Рис. примера 14.40 был получен именно таким способом. Ïðèìåð 14.40. Ðåëüåôíàÿ íàäïèñü íà ñôåðå. import graph3; size(6.5cm,0); currentprojection=orthographic(2,5,2); triple f(pair z){ return (cos(z.x)*sin(z.y),sin(z.x)*sin(z.y) ,cos(z.y));} surface s=surface(f,(-pi,pi),(pi,0),12,Spline); draw(s,lightgreen); //,meshpen=yellow+thick()); draw(surface(xscale(1.7)*scale(0.15)* "$x^2+y^2+z^2=R^2$",s,uoffset=7, voffset=6,height=0.1),red); Глава 14. Модуль graph3 169 Выполняя надпись, можно использовать весь арсенал математических символов и формул, которые способен воспроизвести LATEX. В заключение в примере 14.41 приводится еще одно изображение бутылки Клейна с надписями, которые представляют собой формулы, описывающие эту поверхность. Ïðèìåð 14.41. Íàäïèñè íà áóòûëêå Êëåéíà. import graph3; size(8cm,0); currentprojection=perspective(camera=(25.1,-30.3,19.4),up=Z,target=(-0.6,0.7,-0.63), zoom=1,autoadjust=false); triple f(pair t){real u=t.x; real v=t.y; real r=2-cos(u); real x=3*cos(u)*(1+sin(u))+ r*cos(v)*(u<pi ? cos(u) : -1); real y=8*sin(u)+(u<pi ? r*sin(u)*cos(v) : 0); real z=r*sin(v); return (x,y,z);} surface s=surface(f,(0,0),(2pi,2pi), 8,8,Spline); draw(s,lightolive+white); string lo="$\displaystyle u\in[0,\pi]: \cases{x=3\cos u(1+\sin u)+(2-\cos u)\cos u\cos v,\cr y=8\sin u+(2-\cos u)\sin u\cos v,\cr z=(2-\cos u)\sin v.\cr}$"; string hi="$\displaystyle u\in[\pi,2\pi]:\\\cases{x=3\cos u(1+\sin u)-(2-\cos u)\cos v,\cr y=8\sin u,\cr z=(2-\cos u)\sin v.\cr}$"; real h=0.0125; draw(surface(xscale(-0.38)*yscale(-0.18)*lo,s,0,1.7,h)); draw(surface(xscale(0.26)*yscale(0.1)*rotate(90)*hi,s,4.9,1.4,h)); Ãëàâà 15 Ìîäóëè solids è contour3 В модуле solids определена структура revolution, предназначенная для рисования поверхностей вращения. Прежде всего следует сказать, что такие поверхности вращения как сфера, конус и цилиндр уже представлены в этом пакете соответствующими процедурами. Например, с помощью revolution cylinder(triple c=O, real r, real h, triple axis=Z); { triple C=c+r*perp(axis); axis=h*unit(axis); return revolution(c,C--C+axis,axis); } создается «прозрачный» цилиндр с центром в точке c, радиуса r, высоты h и осью axis (по умолчанию axis=Z). Этот цилиндр можно увидеть на рис. примера 15.1. Интересно, что невидимую линию основания Asymptote по собственному почину рисует штрихами. Ïðèìåð 15.1. ¾Ïðîçðà÷íûé¿ öèëèíäð. import solids; currentprojection=orthographic(5,4,2); size(4cm,0); triple pO=(0,0,0); draw(cylinder(pO,1,2),blue+0.7bp); dot(Label("c",E),pO); dot((0,0,2)); draw(pO--(0,0,2),lightblue+dashed); Чтобы раскрасить «прозрачный» цилиндр, достаточно натянуть на него поверхность, как это сделано в примере 15.2. Аналогично процедура revolution cone(triple c=O, real r, real h, triple axis=Z, int n=nslice); { axis=unit(axis); return revolution(c,approach(c+r*perp(axis)--c+h*axis,n),axis); } 170 Глава 15. Модули solids и contour3 171 Ïðèìåð 15.2. Îêðàøåííûé öèëèíäð. import solids; size(6cm); currentprojection=orthographic(3,4,2); currentlight=Viewport; triple pO=(0,0,0); revolution cyl=cylinder(pO,1,2,Y+Z); draw(surface(cyl),orange); axes3("$x$","$y$","$z$",min=(0,0,0),max=(1.3,2,2), Arrow3); формирует «прозрачный» конус с центром в точке c, радиуса r, высоты h и осью axis (по умолчанию axis=Z). Его изображение приведено в примере 15.3, а окрашенный конус показан на рис. примера 15.4. Ïðèìåð 15.3. ¾Ïðîçðà÷íûé¿ êîíóñ. import solids; currentprojection=orthographic(5,4,2); size(4cm,0); triple pO=(0,0,0); draw(cylinder(pO,1,2),blue+0.7bp); dot(Label("c",W),pO); dot((0,0,2)); draw(pO--(0,0,2),lightblue+dashed); Ïðèìåð 15.4. Îêðàøåííûé êîíóñ. import solids; size(5cm,0); currentprojection=orthographic(5,2,2); revolution CoRev=cone(O,1,2,axis=0.2*Y+2*Z,n=1); draw(surface(CoRev),blue+opacity(0.8)); axes3("$x$","$y$","$z$",min=(0,0,0), max=(1.5,1.35,2.15),Arrow3); Сферу с центром в точке c радиуса r создает процедура Глава 15. Модули solids и contour3 172 revolution sphere(triple c=O, real r, int n=nslice); { return revolution(c,Arc(c,r,180,0,0,0,Y,n),Z); } Результат ее выполнения можно видеть на рис. примера 15.5. Ïðèìåð 15.5. Îêðàøåííàÿ ñôåðà. import solids; size(5cm,0); currentprojection=orthographic(5,4,2); triple pO=(0,0,0); real a=2; revolution s=sphere(pO,a); draw(surface(s),springgreen); В определении последних двух процедур использовался параметр nslice. Его увеличение вообще говоря улучшает качество изображения. Можно сравнить изображение сферы на рис. примера 15.5, в котором было принято значение по умолчанию nslice=12, с ее вариантом на рис. примера 15.6, для которого было взято nslice=2. В последнем примере видны погрешности изображения в виде «ребер» сферы, как будто поверхность склеивали из отдельных частей. Ïðèìåð 15.6. Îêðàøåííàÿ ñôåðà ïðè n=2. import solids; size(5cm,0); currentprojection=orthographic(5,4,2); triple pO=(0,0,0); real a=2; revolution s=sphere(pO,a,n=2); draw(surface(s),springgreen); В общем случае поверхность вращения формируется процедурой revolution(triple c, path3 g, triple axis, real angle1, real angle2); где g – кривая, которая, зачерчивая поверхность, вращается вокруг оси axis, проходящей через точку c, причем угол поворота изменяется (в плоскости, перпендикулярной оси axis) от угла angle1 до угла angle2. В примере 15.7 кривая совершает полный оборот, в примере 15.8 – неполный, в результате чего возникает разрез поверхности. Глава 15. Модули solids и contour3 173 Ïðèìåð 15.7. Ïîâåðõíîñòü âðàùåíèÿ â îáùåì ñëó÷àå. import solids; currentprojection = perspective(10,100,25); unitsize(2.5cm); triple pO=(0,0,0); path3 gene=(0,0.5,-2)..(0,0.5,-1)..(0,1,-0.5)..(0,1,0.5).. (0,0.5,1)..(0,0.5,2); revolution sur=revolution(pO,gene,Z,0,360); draw(surface(sur),red); shipout(bbox(0.01cm+invisible)); Ïðèìåð 15.8. Ïîâåðõíîñòü âðàùåíèÿ ñ ðàçðåçîì. import solids; currentprojection = perspective(10,100,25); unitsize(2.5cm); triple pO=(0,0,0); path3 gene=(0,0.5,-2)..(0,0.5,-1)..(0,1,-0.5)..(0,1,0.5).. (0,0.5,1)..(0,0.5,2); revolution sur=revolution(pO,gene,Z,120,360); draw(surface(sur),magenta); shipout(bbox(0.01cm+invisible)); Имеется также специальная процедура рисования draw, позволяющая получить каркас поверхности в виде линий типа transverse (полученных сечениями, перпендикулярными оси вращения) и линий типа longitudenal (полученных сечениями, параллельными этой оси). Заголовок процедуры: void draw(picture pic=currentpicture, revolution r, int m=0, int n=nslice, pen frontpen=currentpen, pen backpen=frontpen, pen longitudinalpen=frontpen, pen longitudinalbackpen=backpen, light light=currentlight, string name="", render render=defaultrender, projection P=currentprojection); Глава 15. Модули solids и contour3 174 Здесь m – количество линий поперечного сечения. Для таких сечений можно определить перья frontpen и backpen и longitudinalpen и longitudinalbackpen – для продольных сечений. На рис. примера 15.9 показан каркас сферы. Ïðèìåð 15.9. Êàðêàñ ñôåðû. import solids; currentprojection=perspective(10,100,25); size(6cm,0); real a=2.5; revolution r=sphere(O,a); draw(r,10, frontpen=blue+0.7bp, backpen=red+0.7bp, longitudinalpen=magenta+0.7bp, longitudinalbackpen=heavygreen+0.7bp); Пакет contour3 служит для изображения поверхностей, представляющих собой объекты нулевой размерности, описываемые функциями переменных x, y, z или матрицами вида real[][][]. Основная процедура модуля, с таким же названием, имеет следующий заголовок: vertex[][] contour3(real f(real, real, real), triple a, triple b, int nx=nmesh, int ny=nx, int nz=nx, projection P=currentprojection); С помощью данного модуля можно строить неявно заданные поверхности. Однако качество изображений оставляет желать лучшего. Доказательством служит пример 15.10, в котором была сделана попытка нарисовать тор. Значение nx=13 оказалось максимально возможным, дальнейшее его увеличение приводило к переполнению. Ïðèìåð 15.10. Èçîáðàæåíèå òîðà ñ ïîìîùüþ ïðîöåäóðû contour3. import contour3; currentprojection=orthographic(3,1,4); currentlight=Viewport; size(6cm,0); real f(real x,real y,real z){return (x^2+y^2+z^2+5)^2-36*(x^2+y^2);} surface s=surface(contour3(f,(-6,-6,-2),(6,6,2),13)); draw(s,blue); Ãëàâà 16 Ìîäóëè labelpath3 è tube Модуль labelpath3, как уже было сказано в разделе описания всех модулей, расширяет возможности модуля labelpath на трехмерное пространство, причем основная процедура называется почему-то не labelpath3, а по-прежнему – labelpath: surface labelpath(string s, path3 p, real angle=90, triple optional=O); Надпись в виде строки s выводится вдоль пути p. Угол angle регулирует поворот надписи вокруг пути. В примере 16.1 надпись в верхней части рис. выводится под углом −90◦ к пути. Вторая состоит из двух строк и выводится под углом 180◦ к плоскости, в которой лежит окружность. Ïðèìåð 16.1. Íàäïèñè âäîëü ïóòåé â ïðîñòðàíñòâå. import labelpath3; size(5cm,0); path3 g=(1,0,0)..(0,1,1)..(-1,0,0)..(0,-1,1).. cycle; path3 g2=shift(-Z)*reverse(unitcircle3); string txt1="\hbox{This is a test of \emph{curved} 3D labels in \textbf{Asymptote} (implemented with {\tt texpath}).}"; string txt2="This is a test of curved labels in Asymptote\\(implemented without the {\tt PSTricks pstextpath} macro)."; draw(surface(g),paleblue+opacity(0.5)); draw(labelpath(txt1, subpath(g,0,reltime(g,0.95)),angle=-90),orange); draw(g2,1bp+red); draw(labelpath(txt2,subpath(g2,0,3.9),angle=180, optional=rotate(-70,X)*Z)); На рис. примера 16.2 требовалось вывести надпись на плоскость z = 3 − y. Для этого был создан путь lab, который «почти принадлежал» этой плоскости, и вдоль него под углом 135◦ к нему была выведена надпись. Если бы путь принадлежал плоскости, он должен был бы иметь вид (0.6,1.8,1.2)--(-0.6,1.8,1.2), но тогда надпись была бы «утоплена» в плоскости в результате раскраски последней. Полупрозрачность поверхности дала бы возможность ее прочитать, но четкость была бы невысокой. Поэтому надпись приподнята над плоскостью z = 3 − y на 0.02. В примере 16.3 показан один из способов надписывания графика двумерной функции. Из процедуры f, производящей точки поверхности z = (x2 + y 2 )3/2 , была получена функция f1, 175 Глава 16. Модули labelpath3 и tube 176 Ïðèìåð 16.2. Íàäïèñü íà ïëîñêîñòè. import graph3; import labelpath3; import palette; size(5cm,0); currentprojection=orthographic((10,8,5)); currentlight=nolight; pen p3=rgb(.1,.1,.48); pen p4=rgb(.93,.55,.93); pen sface=bp+opacity(.6); real a=sqrt(3), b=1, dy=.5; triple f(pair p){ return (p.x,p.x^2,p.y*(a^2-p.x^2));} triple f0(real t) {return f((t,0));} triple f1(real t) {return f((t,1));} surface s=surface(f,(-a,0),(a,1),100); s.colors(palette(s.map(ypart),Gradient(sface+p3, sface+white,sface+p4))); path3 p0=graph(f0,-a,a,operator ..), p1=graph(f1,-a,a,operator ..); surface surtop=surface(p1--cycle); surtop.colors(palette(surtop.map(zpart), Gradient(sface+p4,sface+white,sface+p3))); draw(s); draw(surtop); draw(p0--cycle ^^ p1,blue); limits((-.5,-.5,-.1),(a,a^2+.5,a^2+.5)); path3 lab=(0.6,1.8,1.22)--(-0.6,1.8,1.22); draw(labelpath("$z=3-y$",lab,angle=135)); которая давала сечение части поверхности плоскостью, параллельной плоскости xOy. Получаемая таким образом кривая, лежащая немного выше изображения поверхности, не чертилась на рисунке, а служила тем путем, вдоль которого располагалась надпись. Модуль tube позволяет конструировать объемные кривые в виде трубок и других профилей. Трубки производит наиболее простой вариант процедуры tube tube(path3 g, real width, render render=defaultrender); Результатом ее выполнения является трубка диаметром width, для которой осью можно приближенно считать кривую g. Структурно трубка, скажем, t состоит из поверхности s и оси center типа path3, к которым можно обращатся как к t.s и t.center, соответственно. На рис. примера 16.4 можно видеть созданную таким образом трубку, причем трубка названа T, ее поверхность T.s раскрашена при помощи палитры BWRainbow, а ее ось T.center выглядывает в виде белой нити из верхнего конца трубки. Рендеринг трубки может выполняться довольно медленно, особенно если использовать растеризатор Adobe Reader. Делу может помочь установка thick=false, делая все линии ширины linewidth(0) (один пиксель независимо от разрешения). По умолчанию в трехмерном случае сетка на поверхности и линии контуров всегда рисуются тонкими, если только перо не настроено по-другому или установлено thin=false. Перья thin() и thick(), определенные в plai_npens.asy, также могут быть переопределены для выполнения специфических команд рисования. Следующая, более продвинутая, процедура задает трубчатую поверхность surface tube(path3 g, coloredpath section, transform T(real)=new transform(real t) return identity();, real corner=1, real relstep=0); Глава 16. Модули labelpath3 и tube 177 Ïðèìåð 16.3. Íàäïèñûâàíèå ãðàôèêà ôóíêöèè. import graph3; import labelpath3; import palette; size(6cm,0,keepAspect=true); currentprojection=orthographic((5,4,2)); currentlight=nolight; pen p2=rgb(.1,.1,.48); pen sface=bp+opacity(0.7); pen mahogany=cmyk(0,0.85,0.87,0.35); triple f(pair rf){ real xx=rf.x*cos(rf.y); real yy=rf.x*sin(rf.y); return (xx,yy,1.6*(xx^2+yy^2)^1.5);} triple f1(real fi){ return f((0.805,fi));} path3 lab=graph(f1,2.92,3.74,operator..); surface s=surface(f,(0,0),(0.79,2pi),8,Spline); s.colors(palette(s.map(ypart),Gradient(sface+p2, sface+white,sface+p2))); draw(s,meshpen=blue+0.3bp); draw(labelpath("$z=(x^2+y^2)^{3/2}$",reverse(lab), angle=180)); axes3(xlabel=Label("$x$",dir(X-O),mahogany),ylabel=Label("$y$",dir(Y-O),mahogany), zlabel=Label("$z$",dir(Z-O),mahogany),min=(0,0,0),max=(0.8,0.8,1.3),orange, Arrow3(mahogany)); Ïðèìåð 16.4. Òðóá÷àòàÿ ïîâåðõíîñòü è åå îñü. import graph3; import palette; size(3cm,0); currentprojection=orthographic(5,4,2); viewportmargin=(1cm,0); real real real real r(real x(real y(real z(real t) t) t) t) {return {return {return {return 3exp(-0.1*t);} r(t)*cos(t);} r(t)*sin(t);} t;} path3 p=graph(x,y,z,0,6*pi,50,operator ..); tube T=tube(p,2); surface s=T.s; s.colors(palette(s.map(zpart),BWRainbow())); draw(s); draw(T.center,white+thin()); Поверхность выстраивается вдоль пути g, имеет профиль (сечение) section, к которому в точке relpoint(g,t) может быть применено преобразование T. Параметр corner управляет числом элементарных трубок в угловых точках g. Отличное от нуля значение relstep определяет относительный шаг по параметру (в смысле relpoint(g,t)), который используется при конструировании элементарных трубок, расположенных вдоль g. Тип coloredpath является обощением пути path и имеет вид следующей структуры: Глава 16. Модули labelpath3 и tube 178 struct coloredpath { path p; pen[] pens(real t); int colortype=coloredSegments; } Здесь p представляет собой путь, расположенный в плоскости, и определяющий профиль трубчатой поверхности; циклический массив перьев pens служит для раскрашивания плиток поверхности в точке relpoint(g,t). Если colortype=coloredSegments, поверхность раскрашивается так, как если бы каждый сегмент сечения раскрашивался заданными перьями pens(t); если же colortype=coloredNodes, то раскраска идет так, как будто раскрашиваются узлы сечения. В примере 16.5 отдельно красным цветом изображена ось двух нарисованных ниже трубок. Для раскрашивания верхней трубки было выбрано раскрашивание на основе сегментов сечения, а для нижней – на основе узлов. Ïðèìåð 16.5. Ðàñêðàñêà îò ñåãìåíòîâ è îò óçëîâ. import graph3; import tube; import palette; size(7cm); currentprojection=orthographic(2,2,2); path3 g=(1,0,0)..(0,1,0.25)..(-1,0,.5).. (0,-1,0.75)..(1,0,1); draw(shift(0,0,0.5)*g,red); pen[] pens; pens[0]=red; pens[1]=blue; pens[2]=yellow; pair pA=(0,0), pB=(1,0), pC=rotate(60,pA)*pB; path sec=pA--pB--pC--cycle; path section=scale(0.15)*sec; coloredpath colorsec=coloredpath(section,pens, colortype=coloredSegments); draw(tube(g,colorsec)); colorsec=coloredpath(section,pens, colortype=coloredNodes); draw(shift((0,0,-0.6))*tube(g,colorsec)); Свой собственный путь coloredpath пользователь может сформировать с помощью одной из трех процедур: coloredpath coloredpath(path p, pen[] pens(real), int colortype=coloredSegments); coloredpath coloredpath(path p, pen[] pens=new pen[] currentpen, int colortype=coloredSegments); coloredpath coloredpath(path p, pen pen(real)); Во второй выбор пера не зависит от относительного значения параметра оси. Что касается третьей процедуры, то массив перьев содержит только одно перо, зависящее от относительного значения параметра. Глава 16. Модули labelpath3 и tube 179 То, что coloredpath наследуется от path, позволяет вместо coloredpath использовать path; в этом случае раскраска берется той, которая по умолчанию используется для поверхности. Следующий пример16.6 показывает, что профиль трубчатой поверхности может определять и символ, в качестве которого выбрано число π. Ïðèìåð 16.6. Ïîâåðõíîñòü ñ ïðîôèëåì ÷èñëà π . import tube; import graph3; import palette; size(7cm); currentlight=White; currentprojection=perspective(1,1,1,up=-Y); int e=1; real x(real t) {return cos(t)+2*cos(2t);} real y(real t) {return sin(t)-2*sin(2t);} real z(real t) {return 2*e*sin(3t);} path3 p=scale3(2)*graph(x,y,z,0,2pi,50, operator..)&cycle; pen[] pens=Gradient(6,red,blue,purple); pens.push(yellow); for (int i=pens.length-2; i>=0; --i) pens.push(pens[i]); path sec=scale(0.25)*texpath("$\pi$")[0]; coloredpath colorsec=coloredpath(sec,pens, colortype=coloredSegments); draw(tube(p,colorsec),render(merge=true)); Еще один пример 16.7 демонстрирует кольца Борромео. Ïðèìåð 16.7. Êîëüöà Áîððîìåî. import graph3; import tube; size(8cm); currentprojection=orthographic(2,2,2); real a=2, b=1.5; real x(real t) {return a*cos(t);} real y(real t) {return b*sin(t);} real z(real t) {return 0;} path3 an1=graph(x,y,z,0,2*pi); path3 an2=graph(z,x,y,0,2*pi); path3 an3=graph(y,z,x,0,2*pi); draw(tube(an1,scale(0.1)*unitcircle),red); draw(tube(an2,scale(0.1)*unitcircle),blue); draw(tube(an3,scale(0.1)*unitcircle),yellow); Применяя полученные сведения о построении поверхностей вращения, трубчатых поверхностей, методах их раскрашивания и освещения, можно создавать почти художественные произведения, см. Приложение D. Ïðèëîæåíèå A Ïëàí êâàðòèðû Ïëàí êâàðòèðû. settings.outformat="pdf"; settings.prc=false; size(8.5cm,0); defaultpen(fontsize(10pt)); // Âíåøíèå ñòåíû draw((0,0)--(6.5,0)--(6.5,1)--(6.8,1)--(6.8,1.3)--(6.5,1.3)--(6.5,9)--(0.3,9)-(0.3,9.5)--(0,9.5)--(0,9.1)--(-1.2,9.1)--(-1.2,8.8)--(0,8.8)--(0,3.5)--(-1.2,3.5) --(-1.2,3.2)--(0,3.2)--cycle); draw((0.3,0.3)--(6.2,0.3)--(6.2,8.7)--(0.3,8.7)--cycle); // Âíóòðåííèå ñòåíû draw((3.3,0.3)--(3.3,8.7)^^(3.45,0.3)--(3.45,8.7)); draw((0.3,4)--(3.3,4)^^(0.3,4.15)--(3.3,4.15)); draw((3.45,3)--(6.2,3)^^(3.45,3.1)--(6.2,3.1)); draw((4.6,3.1)--(4.6,7)--(6.2,7)^^(4.7,3.1)--(4.7,6.9)--(6.2,6.9)); draw((4.7,3.7)--(6.2,3.7)^^(4.7,3.8)--(6.2,3.8)); draw((4.7,5.2)--(6.2,5.2)^^(4.7,5.3)--(6.2,5.3)); draw((5.35,5.3)--(5.35,6.9)^^(5.4,5.3)--(5.4,6.9)); draw((4.7,6.25)--(5.35,6.25)^^(4.7,6.3)--(5.35,6.3)); // Áàëêîí draw((-1,8.8)--(-1,3.5)^^(-0.87,8.8)--(-0.87,3.5)); // Îêíà path[] window=(1.6,0)--(1.6,0.3)^^(2.9,0)--(2.9,0.3)^^(1.6,0.1)--(2.9,0.1)^^ (1.6,0.2)--(2.9,0.2); draw(window); draw(shift(2.2,0)*window); draw(shift(0.3,4.4)*rotate(90)*window); draw(yscale(0.7)*shift(0.3,9)*rotate(90)*window); draw((-0.2,7.85)--(0.5,7.85)); // Äâåðè path[] door=(3.3,8.3)--(3.45,8.3)^^(3.3,7.6)--(3.45,7.6)^^(3.15,7.95)--(3.6,7.95); draw(door); draw(shift(0,-4.4)*door); draw((6.2,8.5)--(6.5,8.5)); draw((6.2,7.7)--(6.5,7.7)); draw((6.05,8.1)--(6.65,8.1)); draw((5.6,6.9)--(5.6,7)); draw((6,6.9)--(6,7)); draw((5.8,6.8)--(5.8,7.1)); 180 Приложение A. План квартиры 181 3,16 2 12,7 3,22 3 7,4 2,74 4,81 1 15,2 3,95 h = 2,70 2,70 path[] doors=(4.6,6.8)--(4.7,6.8)^^(4.6,6.4)--(4.7,6.4)^^(4.5,6.6)--(4.8,6.6); draw(doors); draw(shift(0,-0.8)*doors); draw(yscale(1.2)*shift(0,-3.1)*doors); draw(shift(0,-3.2)*doors); draw((3.8,3)--(3.8,3.1)); draw((4.3,3)--(4.3,3.1)); draw((4.05,2.9)--(4.05,3.2)); // Âàííàÿ draw((5.5,3.9)--(6.1,3.9)--(6.1,5.1)--(5.5,5.1)--cycle); path[] umiv=(4.9,5.2)--(4.9,4.8)--(5.3,4.8)--(5.3,5.2)^^(5,5.2)--(5,4.95)^^ (4.95,4.95)--(5.05,4.95)^^(5.2,5.2)--(5.2,4.95)^^(5.15,4.95)--(5.25,4.95); draw(umiv); draw(shift(1,7.8)*rotate(-90)*umiv); // Êóõíÿ draw((5.7,1.8)--(6.1,1.8)--(6.1,2.2)--(5.7,2.2)--cycle); path unitcircle=(-0.05,0)..(0,0.05)..(0.05,0)..(0,-0.05)..cycle; draw(shift(5.8,1.9)*unitcircle); draw(shift(6,1.9)*unitcircle); draw(shift(5.8,2.1)*unitcircle); draw(shift(6,2.1)*unitcircle); // Òóàëåò real a=5.6,b=5.5; draw((a,b)--(a,b+0.4)..(a+0.2,b+0.6)..(a+0.4,b+0.4)--(a+0.4,b)--cycle); draw((a,b+0.15)--(a+0.4,b+0.15)); draw(shift(a+0.2,b+0.35)*scale(1.5)*unitcircle); // Ðàçìåðû draw((0.3,-0.35)--(3.3,-0.35),L=Label("3,22",Center,filltype=UnFill), Arrows(1.5mm),Bars); draw((3.5,-0.35)--(6.2,-0.35),L=Label("2,70",Center,filltype=UnFill), Arrows(1.5mm),Bars); draw((0.3,9.3)--(3.3,9.3),L=Label("3,16",Center,filltype=UnFill),Arrows(1.5mm),Bars); Приложение A. План квартиры 182 draw((1,0.3)--(1,4),L=Label(rotate(90)*"3,95",Center,filltype=UnFill),Arrows(1.5mm)); draw((1,4.2)--(1,8.7),L=Label(rotate(90)*"4,81",Center,filltype=UnFill), Arrows(1.5mm)); draw((5.4,0.3)--(5.4,3),L=Label(rotate(90)*"2,74",Center,filltype=UnFill), Arrows(1.5mm)); label("$h=2{,}70$",(2.15,8.3)); label("$\displaystyle{\frac{1}{15{,}2}}$",(2.15,6.45)); label("$\displaystyle{\frac{2}{12{,}7}}$",(2.15,2.15)); label("$\displaystyle{\frac{3}{7{,}4}}$",(4.45,1.65)); shipout(bbox(0.1cm+invisible)); Конечно, представленный вариант программирования плана квартиры вполне примитивен, так как играет здесь лишь иллюстративную роль. Разумнее было бы обратиться к возможностям создания картинок picture и добавления их к основному рисунку (см. раздел 7.7). Предметы мебели, ванну, газовую плиту, окна, двери и т. д. можно было бы изобразить в виде отдельных картинок, а затем с помощью перемещений и поворотов расположить в требуемом месте плана. Другим способом является написание специальных процедур рисования, параметры которых определяли бы центр изображаемого объекта на плане, его относительные размеры и ориентацию. Ïðèëîæåíèå B Çà÷åð÷èâàíèå I Построим и окрасим цилиндроид, ограниченный сверху поверхностью z = f (x, y) = 12 − 0.08x2 , а с боков – цилиндрической поверхностью (23 + 3x)y 2 = 12(36 − (x − 1)2 ); (B.1) при этом x ∈ [−5; 7], y ∈ [−5; 5]. Уравнение (B.1) позволяет записать контур, ограничивающий основание цилиндроида, в виде r 12(36 − (x − 1)2 ) y=± . (B.2) 23 + 3x Далее представим себе, что боковая поверхность цилиндроида образуется с помощью вертикального отрезка, перемещающегося вдоль этого контура параллельно самому себе. Для этого требуется найти параметризацию, позволяющую записать контур (B.2) как одно целое, т. е. как явную функцию, а не как многозначное отображение (B.2). Пусть параметр u принимает значения из отрезка [0; 2], а параметризованное значение x имеет вид −12u + 7, u ∈ [0; 1], x= 12u − 17, u ∈ [1; 2]. При изменении u от 0 до 2 переменная x изменяется сначала от 7 до −5, а затем наоборот – от −5 до 7. Теперь функцию (B.2) можно записать как s 12(36 − (x(u) − 1)2 ) y(u) = sign(1 − u) . 23 + 3x(u) Так как вертикальный отрезок [0; f (x, y)] можно задать с помощью еще одного параметра t ∈ [0; 1] в виде tf (x, y), то в целом боковая поверхность цилиндроида описывается тройкой координат (x(u), y(u), tf (x(u), y(u))), u ∈ [0; 2], t ∈ [0; 1]. Верхнюю часть цидиндроида получим уже известным способом, натянув на линию пересечения верхней его части и боковой поверхности вида (x(u), y(u), f (x(u), y(u))), 183 u ∈ [0; 2], Приложение B. Зачерчивание I 184 поверхность с помощью surface. Предварительно указанную линию пересечения необходимо, конечно, зациклить. Далее следует текст программы и результат ее работы в виде рисунка. Ïîñòðîåíèå è ðàñêðàñêà öèëèíäðîèäà. // Ðàçëè÷íûå óñòàíîâêè settings.outformat="pdf"; settings.prc=false; settings.render = 4; // Ïîäêëþ÷åíèå ìîäóëåé Asymptote import three; import graph3; import palette; // Óñòàíîâêà ðàçìåðà êàðòèíêè, òèïà ïðîåêöèè è îñâåùåíèÿ size(8cm,12cm,keepAspect=true); currentprojection=orthographic((2,-4.3,3.2)); currentlight=nolight; // Îïðåäåëåíèå ôóíêöèè y = sign(x) real sign(real x){ return (x<0) ? -1. : 1.; } // Çàäàíèå ôóíêöèè z = 12 - 0.08*x^2 real f(real x,real y){ return 12-0.08*x^2; } // Âû÷èñëåíèå àáñöèññû êîíòóðà îñíîâàíèÿ x = tau(t). // Êîãäà ïåðåìåííàÿ t ïðîõîäèò îòðåçîê [0,2], ïåðåìåííàÿ x ïðîõîäèò îòðåçîê [-5,7] // ñíà÷àëà â îäíîì íàïðàâëåíèè, à çàòåì - â ïðîòèâîïîëîæíîì. real tau(real t){ return (t<1) ? -12*t+7 : 12*t-17; } // Îðäèíàòà êîíòóðà îñíîâàíèÿ real cont(real t){ return sign(1-t)*sqrt(12*(36-(tau(t)-1)^2)/(23+3*tau(t))); } // Êîîðäèíàòû òî÷åê áîêîâîé ïîâåðõíîñòè öèëèíäðîèäà triple cheek(pair ut){ real u=ut.x; real t=ut.y; real xx=tau(u); real yy=cont(u); return (xx,yy,t*f(xx,yy)); } // Êîîðäèíàòû ëèíèé ïåðåñå÷åíèÿ áîêîâîé ïîâåðõíîñòè ñ îñíîâàíèåì è âåðõíåé ÷àñòüþ // öèëèíäðîèäà triple cheek0(real u) {return cheek((u,0));} triple cheek1(real u) {return cheek((u,1));} Приложение B. Зачерчивание I // Çàäàíèå íåîáõîäèìûõ äëÿ ðèñîâàíèÿ ïåðüåâ pen p3=rgb(.21,.88,1.); pen p4=rgb(.68,1.,1.); pen tcol=darkgray; pen p1=rgb(.1,.1,.48); pen p2=rgb(.93,.55,.93); pen pen pen pen mahogany=cmyk(0,0.85,0.87,0.35); conts=blue; decomp=black; sface=bp+opacity(.2); // Áîêîâàÿ ïîâåðõíîñòü surface cheekside=surface(cheek,(0,0),(2,1),50,Spline); // Ëèíèè ïåðåñå÷åíèÿ áîêîâîé ïîâåðõíîñòè ñ îñíîâàíèåì è âåðõíåé ÷àñòüþ öèëèíäðîèäà path3 botline=graph(cheek0,0,2,operator..); path3 topline=graph(cheek1,0,2,operator..); // Âåðõíÿÿ ÷àñòü ïîâåðõíîñòè surface topside=surface(topline--cycle); // Ôóíêöèè, îïèñûâàþùèå êðèâûå ðàçáèåíèÿ îñíîâàíèÿ öèëèíäðîèäà íà ÷àñòè real xh1(real x) {return x;} real yh1(real x) {return 0.05*x^2+2.0813;} real zh1(real x) {return 0;} real yh2(real x) {return 0.05*x^2;} real yh3(real x) {return 0.05*x^2-2.5;} real xv2(real y) {return 0.05*y^2+0.5;} real xv3(real y) {return 0.05*y^2+3.745;} // Êðèâûå ðàçáèåíèÿ îñíîâàíèÿ öèëèíäðîèäà íà ÷àñòè path3 hor1=graph(xh1,yh1,zh1,-4.32,4.18174,operator..); path3 hor2=graph(xh1,yh2,zh1,-4.92,6,operator..); path3 hor3=graph(xh1,yh3,zh1,-4.9,7,operator..); path3 vert1=graph(yh3,xh1,zh1,-4.39,4.39,operator..); path3 vert2=graph(xv2,xh1,zh1,-4,4,operator..); path3 vert3=graph(xv3,xh1,zh1,-2.97,2.9556,operator..); // Îïèñàíèå îêðàñêè ïîâåðõíîñòè cheekside.colors(palette(cheekside.map(xpart),Gradient(sface+p1, sface+white,sface+p2))); topside.colors(palette(cheekside.map(xpart),Gradient(sface+p2, sface+white,sface+p1))); // Ñîáñòâåííî ðèñîâàíèå êðèâûõ è ïîâåðõíîñòåé draw(cheekside); draw(topside); draw(hor1,decomp); draw(hor2,decomp); draw(hor3,decomp); draw(vert1,decomp); draw(vert2,decomp); draw(vert3,decomp); 185 Приложение B. Зачерчивание I draw(botline,conts); draw(topline,conts); // Ðèñîâàíèå îñåé, íàäïèñåé è ò.ï. draw ((-5.4,5.2,0)--(-5.5,-4.5,0),mahogany,Arrow3(size=3mm), L=Label("$x$",position=EndPoint,align=SW)); draw ((-5.4,5.2,0)--(6.7,5.2,0),mahogany,Arrow3(size=3mm), L=Label("$y$",position=EndPoint,align=E)); draw ((-5.4,5.2,0)--(-5.4,5.2,11.5),mahogany,Arrow3(size=3mm), L=Label("$z$",position=EndPoint,align=N)); draw((1.5,5,10)--(1.5,6.5,10.5)); label("$z=f(x,y)$",(0.3,6.5,10.3),NE,tcol); label("O",(-5.6,5.2,0.4),W,mahogany); label("$D$",(-2.2,-5.5,-0.3),tcol); label(rotate(-18)*"$\Delta S_i$",(0.4,0.6,0.5),W,red); // Îãðàíè÷èâàþùèé ðèñóíîê ïðÿìîóãîëüíèê shipout(bbox(0.01cm+invisible)); 186 Ïðèëîæåíèå C Çà÷åð÷èâàíèå II . В отличие от поверхности, заданной функцией двух переменных и построенной в Приложении B, рассмотрим поверхность, определяемую сплайнами. Пояснения будем делать, постепенно разворачивая листинг программы. Вначале выполним все предварительные установки: Ðèñóíîê ê ôîðìóëå Îñòðîãðàäñêîãî. settings.outformat="pdf"; settings.prc=false; settings.render = 4; import three; import graph3; import palette; size(0,7cm); currentprojection=orthographic((1,-3,1.3)); currentlight=nolight; pen mahogany=cmyk(0,0.85,0.87,0.35); pen p1=mediumblue;//rgb(.1,.1,.48); pen p2=mediumblue; pen sface=bp+opacity(0.7); Далее зададим с помощью сплайнов контур cont, расположенный в плоскости xOy (на рис. C.1 он ограничивает область D). Нарисуем этот контур с натянутой на него поверхностью, что́ и даст нам изображение области D. Затем поднимем контур на высоты a и b над xOy и зафиксируем его в этих положениях как contUp и contTop: path3 cont=(-2,0,0)..(-1,1,0)..(0,2,0)..(1.5,1.5,0)..(2,0,0)..(1.5,-0.3,0).. (0,-2,0)..(-1.3,-1.7,0)..cycle; real a=3,b=5; draw(cont,blue+thick()); draw(surface(cont),palegreen); 187 Приложение C. Зачерчивание II 188 path3 contUp=shift((0,0,a))*cont; path3 contTop=shift((0,0,b))*cont; draw(contUp,blue+thick()); draw(contTop,blue+thick()); Теперь опишем функции, которые определят нам поверхность, натянутую на контур cont (стоит напомнить, что этот контур лежит в плоскости xOy). Поверхность сформируем следующим образом. В зависимости от значения параметра t, 0 ≤ t ≤ 0.5, на контуре выберем две точки, n1(x1,y1,0) и n2=(x1,y1,0) с помощью функции relpoint: n1=relpoint(cont,t), n2=relpoint(cont,1-t). Отрезок, концами которого являются эти точки, задается равенствами x=x1+(x2-x1)*tau, y=x1+(y2-y1)*tau, где tau – параметр, принимающий значения в промежутке [0; 1]. Над отрезком построим параболу, проходящую через точки n1 и n2 и имеющую вершину в середине отрезка высоты zmax (t). Движение параболы в соответствии с изменением параметра t и вычертит поверхность над контуром cont. Эти построения выполняют функции taum, app и f. Функция taum определяет наивысшую точку параболы для данного значения t (наивысшей точкой поверхности является taumaxmax), функция app находит аппликату z для ординаты y упомянутого выше отрезка, а f выдает конечный итог – точку поверхности hatside. Еще одна функция, g, возвращает точки вертикального отрезка, который чертит боковую поверхность. real taumaxmax=1.5; real taum(real t){ return -16*taumaxmax*t*(t-0.5); } real app(real taum,real y,real y1,real y2){ real z; if (fabs(y1-y2)>0.0000001) z=-4*taum*(y^2-(y1+y2)*y+y1*y2)/(y1-y2)^2; else z=0; return z; } triple f(pair t_tau){ real t=t_tau.x; real tau=t_tau.y; real taumax=taum(t); triple n1=relpoint(cont,t); triple n2=relpoint(cont,1-t); real x1=n1.x; real y1=n1.y; real x2=n2.x; real y2=n2.y; real x=x1+(x2-x1)*tau; real y=y1+(y2-y1)*tau; real z=app(taumax,y,y1,y2); return (x,y,z); } triple g(pair t_tau){ real t=t_tau.x; real tau=t_tau.y; triple N=relpoint(cont,t); return (N.x,N.y,a+(b-a)*tau); } Приложение C. Зачерчивание II 189 Фактически же боковая поверхность изображается с помощью операторов surface cheekside=surface(g,(0,0),(1,1),50,Spline); cheekside.colors(palette(cheekside.map(xpart),Gradient(sface+heavycyan, sface+white,sface+heavycyan))); draw(cheekside); Верхняя часть поверхности получается сдвигом описанной выше поверхности hatside на b вверх: surface hatside=surface(f,(0,0),(0.5,1),50,Spline); hatside.colors(palette(hatside.map(xpart),Gradient(sface+p1, sface+white,sface+p2))); draw(shift((0,0,b))*hatside); Нижнюю ее часть upside получим отражением поверхности hatside от плоскости xOy (определяемой точками pA, pB и pC) и сдвигом вверх на a: triple pA=(-2,0,0),pB=(0,-2,0),pC=(0,2,0); surface upside=shift((0,0,a))*reflect(pA,pB,pC)*hatside; upside.colors(palette(upside.map(xpart),Gradient(sface+p1, sface+white,sface+p2))); draw(upside); Остается добавить векторы и надписи: pair t_tau_1=(0.43,0.5); triple pn1=f(t_tau_1)+(0,0,b); draw(pn1--pn1+(0.7,0.3,0.7),red+thick(),Arrow3(2mm),L=Label("$\mathbf{n}$", EndPoint)); pair t_tau_2=(0.45,0.7); triple pn2=shift((0,0,a))*reflect(pA,pB,pC)*f(t_tau_2); draw(pn2--pn2+(0.7,0.3,-0.8),red+thick(),Arrow3(2mm),L=Label("$\mathbf{n}$", EndPoint)); pair t_tau_3=(0.5,0.5); triple pn3=g(t_tau_3); draw(pn3--pn3+(0.8,0.3,0),red+thick(),Arrow3(2mm),L=Label("$\mathbf{n}$", EndPoint)); label("$D$",(0.2,-0.5,0.3)); label(rotate(10)*"$z=\beta(x,y)$",(0.8,-2,6)); label(rotate(10)*"$z=\alpha(x,y)$",(1,-2,2.95)); label("$V$",(0,0,(a+b)/2-0.3)); draw((-3,0.5,0)--(-2.5,-3,0),mahogany,Arrow3(size=2mm),L=Label("$x$", position=EndPoint,align=SSW)); Приложение C. Зачерчивание II draw((-3,0.5,0)--(-1.5,2,-0.1),mahogany,Arrow3(size=2mm),L=Label("$y$", position=EndPoint,align=E)); draw((-3,0.5,0)--(-3,0.5,5.7),mahogany,Arrow3(size=2mm),L=Label("$z$", position=EndPoint,align=N)); label("$O$",(-3,0.5,0),W,mahogany); shipout(bbox(0.01cm+invisible)); Рис. C.1. К формуле Остроградского. 190 Ïðèëîæåíèå D ×àøêà ñ íàïèòêîì Дабы завершить труды немалые по написанию труда сего, увенчаем оный столь незамысловатой, но приятной наградой в виде чашечки кофе. Вначале, как водится, пропишем необходимые установки в программе: ×àøêà ñ íàïèòêîì. settings.outformat="pdf"; settings.prc=false; import solids; import graph3; import palette; currentprojection=perspective(5,0,5.1); currentlight=light((10,10,3),(0,-2,10)); size(15cm,0); pen coffee=cmyk(0,0.5,1,0.45); Рисунок начнем с изображения блюдца, для чего зададим кривую saucer, описывающую поперечное сечение блюдца. Кривую с помощью revolution заставим вращаться вокруг оси Oz, после чего натянем на вращение поверхность ssauc. Зададим палитру для раскраски блюдца, используя map и Gradient, и выведем его на экран: path3 saucer=(0,0,0)--(0,3.7,0)..(0,4,-0.4)..(0,4.3,0)..(0,5.4,0.6)..(0,7,1.5)-(0,7,1.55)..(0,5.4,0.75)..(0,4.3,0.15)--(0,2.7,0.15)..(0,2.5,0.1)--(0,0,0.1); revolution sauc=revolution(O,saucer,Z,0,360); surface ssauc=surface(sauc); ssauc.colors(palette(ssauc.map(xpart),Gradient(mediumblue,lightblue,mediumblue))); draw(ssauc); Таким же способом создадим и чашку: path3 cupcurve=(0,0,0)--(0,3.4,0)--(0,3.4,0.3)..(0,3.9,1.2)..(0,5,6.5); revolution cup=revolution(O,cupcurve,Z,0,360); surface scup=surface(cup,n=30); scup.colors(palette(scup.map(ypart),Gradient(mediumgray,lightgray,mediumgray))); draw(scup); 191 Приложение D. Чашка с напитком 192 Трансформации unitdisk (единичного круга) позволят налить в чашку напиток, напоминающий кофе. А еще для «оживляжа» нарисуем на поверхности чашки три синих ободка: surface scoffee=shift(0,0,5.8)*scale3(4.9)*unitdisk; draw(scoffee,darkbrown); draw(shift(0,0,5.6)*scale3(4.99)*unitcircle3,royalblue+3bp); draw(shift(0,0,5.2)*scale3(4.99)*unitcircle3,royalblue+3bp); draw(shift(0,0,2.9)*scale3(4.57)*unitcircle3,royalblue+3bp); Остается пркрепить к чашке ручку. Тут-то и пригодится трубчатая поверхность! path3 handle=(0,4,1.8){dir(70,90)}..(0,6,5.5)..{dir(-80,90)}(0,5,4.9); tube T=tube(handle,0.7); surface s=T.s; s.colors(palette(s.map(ypart),Gradient(lightgray,palegray))); draw(s); shipout(bbox(0.1cm+invisible)); А вот и завершающий аккорд: Ëèòåðàòóðà [1] Asymptote: the Vector Graphics Language Официальное руководство. [2] Charles Staats III. An Asymptote tutorial, 2014. [3] Christophe Grospellier. Asymptote. Démarrage «rapide», 2014. [4] Bruno M. Colombel. Asymptote — 3D, 2011. [5] Ondřej Kutal. Tvorba matematické grafiky pomocí programu Asymptote.– Diplomová práce, Univrsitas Masarykiana brunensis, Brno, 2012. [6] Ю.Г. Крячков. Асимптота для начинающих. Создание рисунков на языке векторной графики Asymptote, 2014. [7] Филипп Ивальди. Евклидова геометрия на языке векторной графики Asymptote.– Волгоград, 2012. 193