Научная графика на языке Asymptote

advertisement
Научная графика на языке
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
Related documents
Download