Главная страница «Первого сентября»Главная страница журнала «Информатика»Содержание №3/2008


Предлагаю коллегам

Язык трехмерного моделирования VRML и его образовательные возможности

Знание некоторых принципов легко возмещает незнание некоторых фактов.
Гельвеций
Всегда считалось, что грамотность, например, доступна лишь тем, кто обладает умственными способностями, особенно подготовленными для восприятия сложных задач чтения и письма. Разумеется, с появлением книгопечатания и массового образования выяснилось, что грамотными в состоянии стать большинство людей.
Айзек Азимов
Ну что просить-то будешь, служивый? Только попроще чего, а то просят телевизоры какие-то, транзисторы... Один совсем обалдел: “Выполни, говорит, за меня годовой план по лесопилке”...
— Ага, — сказал я. — А телевизор вы, значит, все-таки не можете?
— Нет, — честно призналась щука. — Телевизор не могу. И этот... комбайн с проигрывателем тоже не могу. Не верю я в них. Ты чего-нибудь попроще. Сапоги, скажем, скороходы или шапку-невидимку...
Аркадий и Борис Стругацкие

Данная статья продолжает наше знакомство с VRML, которое было начато в предыдущем номере газеты. Сегодня автор призывает, отбросив все необоснованные страхи, создать собственные объемные объекты и убедиться, что все не так сложно, как порой первоначально кажется.

Конечно, в статье мы ознакомимся только с введением в объемную (в прямом и в переносном смысле) книгу под названием VRML. Но зато эти знания дадут возможность в дальнейшем без особых затруднений ее при необходимости читать.

По ходу изложения придется неоднократно ссылаться на материал, опубликованный в первой части статьи. Договоримся везде обозначать эту ссылку максимально кратко — в части 1.

О методике изложения

Вашему вниманию предлагается “неформальное” введение в язык VRML, которое отличается от традиционных способов изложения. По крайней мере большинство материалов в Сети довольно четко отождествляются с одной из двух групп: “VRML в примерах” и группе с несколько условным названием “36 узлов VRML 1.0 1”, которая по сути является слегка расширенным переводом технической спецификации. Разумеется, эти два подхода могут объединяться, но к пониманию это приближает не сильно: обоим подходам не хватает некоторого обобщения.

В первом подходе с многочисленными примерами неявно предполагается, что все обобщения сложатся в голове человека, разобравшегося с этими примерами. Теоретически это даже может произойти, но при совпадении целого ряда факторов: примеров достаточно много, и они настолько разнообразны, что охватывают все основные особенности; изучающий примеры достаточно вдумчив и не поленится разобрать все примеры (или хотя бы большую их часть). Полагаю, что сочетание всего перечисленного сразу по разным причинам имеет невысокую вероятность появления.

Второй подход, несмотря на полноту предложенного материала, также далек от оптимального. Фактов, как всегда, много, все сразу не запомнишь; и опять почти вся работа по их классификации ложится на обучаемого. Его первое впечатление о предмете нетрудно предсказать — куча разрозненных фактов, а я не прочел еще и половины…

В прекрасном фильме [1] по методике обучения показан следующий замечательный пример: предлагается запомнить приведенную на рисунке систему кодирования цифр и затем с ее помощью записать несколько чисел. Результаты оказываются весьма посредственными. Но когда участникам эксперимента показывают рисунок, который поясняет, откуда взялись “таинственные значки”, то оказывается, что и запоминать-то особенно нечего…

Характерно, что описанные выше трудности в методике изложения не есть особенности VRML, о котором мы сейчас говорим. Возьмите любой язык программирования, и там тоже обнаружатся подобные подходы (“изучаем язык X на примерах” или “полное описание всех конструкций языка X”). А “рецептурное” освоение Windows “для чайников” вам ничего не напоминает?

Итак, не хватает систематизации материала, некоторого своеобразного “каркаса”, этаких “ящиков с подписями”, куда мы сможем раскладывать наши фактические знания и примеры. А уж извлечение из хорошо структурированного хранилища данных происходит легко и быстро!

По-видимому, развитыми способностями к систематизации обладают далеко не все люди, даже среди тех, кто пишет статьи (это особенно заметно в Интернете, где нет никакого критического отбора авторов). К счастью, они все же есть; в частности, про VRML можно привести в качестве примера статьи А.Авдуевского [2] и А.Медведева [3], которые очень сильно помогают в понимании базовых принципов языка. Именно принципы и будут нашей главной целью, а зная их, уже можно найти и разобраться в тех из пресловутых 36 (или даже большем числе) узлов, которые вам конкретно понадобились!

В последнее время много (по мнению автора, даже чрезмерно много) говорят о технологиях. Следует четко понимать, что технология есть готовое знание, кем-то проанализированное и сформулированное. Целью же обучения, напротив, является формирование у обучаемых способности самостоятельно систематизировать, обобщать и подбирать необходимые в данной ситуации знания. Следовательно, роль технологий не следует абсолютизировать!

Общие основы VRML

1. Общие правила VRML-описаний (программ). Как мы уже знаем из части 1, описание VRML-объектов (мира) хранится в текстовом файле с расширением WRL; для сокращения времени загрузки текст часто передается в архивированном виде, поэтому не все файлы с этим расширением, которые были “закачаны” из Сети, можно непосредственно прочитать в Блокноте. В простейшем случае мир — это автономный файл, но часто он ссылается на некоторые графические файлы (текстуры), и значительно реже — на выделяемые в отдельный файл WRL объекты-прототипы (ссылка на них обозначается EXTERNPROTO). В данной статье мы ограничимся случаем простейших автономных VRML-файлов.

Примечание. Договоримся не делать особой разницы между терминами “описание VRML-мира” и “VRML-программа”.

Каждый файл с VRML-программой может содержать следующие части:

  • заголовок;

  • описание прототипов;

  • описание сцены;

  • маршруты событий.

Формально обязательным является лишь заголовок; в подавляющем большинстве файлов 2 есть описание сцены. Остальные разделы появляются при необходимости.

Примечание. Об описании прототипов говорится в примере 6.2.2, а о маршрутах событий — в 6.1.5.

Заголовок всех файлов практически одинаков и имеет вид:

#VRML V2.0 utf8

Внимание! Между словами везде стоит ровно один пробел!

Кроме очевидного указания на язык и его версию (мы везде будем использовать стандарт VRML97, который маркируется как версия 2.0), в заголовке задается кодировка символов. Как правило, используется набор символов Unicode, что при работе с русскими буквами будет приводить к некоторым особенностям. В данной статье мы не будем их касаться, поскольку с текстами, “живущими” в виртуальных мирах, и без кодировки слишком много проблем (см. пример в разделе 3).

Знак “#” выделяет комментарии, и поэтому (исключая заголовок!) интерпретатор VRML игнорирует любой текст, стоящий в строке правее. В многострочных комментариях, как нетрудно догадаться, надо пометить каждую строку.

Символы пробела и табуляции являются разделителями; их количество произвольно. В частности, при оформлении текстов можно пользоваться отступами, как это делается в языках типа Паскаля.

Таким же разделителем, согласно описаниям, является переход на новую строку. Тем не менее в отдельных случаях при переносе на новую строку части единой конструкции у меня иногда возникали трудности: изображение “портилось”.

При разделении последовательности чисел наравне с пробелами могут использоваться запятые. Обычно рекомендуют их для наглядности использовать с целью выделения троек координат отдельных точек, например:

0 0 0, 0 0.5 0, 0 1 0

Попутно заметим, что для записи чисел используется традиционная для языков программирования нотация, из которой наиболее важно твердо помнить, что целая и дробная части числа разделяются точкой.

Если верить известному киногерою Штирлицу, лучше всего запоминаются последние слова. Поэтому завершим этот раздел самым важным правилом.

Язык VRML является регистрово-зависимым, т.е. во всех конструкциях заглавные и строчные буквы различаются! Поэтому при наборе любого примера обязательно(!) обращайте внимание, где набирать заглавные, а где строчные буквы!!!

2. Представление величин виртуального мира. При описании виртуальных миров имеется возможность задавать следующие величины: координаты и углы, цвет, а также некоторые физические характеристики вроде прозрачности и светимости. Все перечисленное в VRML изображается вещественными числами.

Координаты трехмерной точки представляют собой совокупность трех произвольных вещественных чисел, включая отрицательные. Часто обсуждается вопрос о том, что эти числа желательно указывать в метрах, мотивируя тем, что при слиянии двух разных миров будет соблюден относительный масштаб. Этой рекомендации не стоит уделять особого внимания, причем не только потому, что различные миры на практике редко объединяют. Гораздо существеннее, что каждый создаваемый мир имеет свой собственный характерный масштаб, которому единица измерения 1 метр не обязательно подходит: возьмите Солнечную систему или объемную модель молекулы — примеры, о которых шла речь в части 1. Поэтому более глубокий подход заключается в том, чтобы в вашем виртуальном мире выбрать некоторый характерный масштаб и принять его за единицу, а все остальные величины соотносить с ним. В частности, в Солнечной системе удобно связать единицы измерения с расстоянием от Земли до Солнца (ученые называют среднее расстояние между данными планетами астрономической единицей) или, может быть, принять за единицу весь размер Солнечной системы, т.е. максимальное удаление Плутона от Солнца.

Примечание. Интересно, что для координат предлагается иметь физическую единицу измерения, а для яркостных характеристик — нет.

Зато учитывать единицу измерения углов необходимо совершенно обязательно: все углы в VRML измеряются не в градусах, а в радианах! В частности, вместо 90° приходится писать 1.57, т.е. приближенное значение /2. Те, кто не любит стереометрию, могут составить (с помощью компьютера) таблицу для значений характерных углов.

Цвет, как и в большинстве компьютерных приложений, представляется в модели RGB. Тем не менее интенсивность компонентов указывается не в целых числах, а в дробных, заключенных к тому же в диапазоне от 0 до 1. Наиболее простые цвета выглядят так:

0 0 0 — черный

1 1 1 — белый

1 0 0 — красный

0 1 0 — зеленый

0 0 1 — синий

0.4 0.4 0.4 — оттенок серого и т.д.

Аналогично цветовым компонентам, физическим свойствам яркости, прозрачности и т.д. также ставятся в соответствие условные дробные величины от 0 до 1. Например, прозрачность 1 делает тело полностью прозрачным, 0 — абсолютно непрозрачным, а значение 0.3 задает некоторый промежуточный уровень.

3. Оси координат. Вопрос этот существенно запутали сами авторы языка. Так в версиях 1.0 и 2.0 направление оси z является противоположным (см. рисунок).

Если вы думаете, что данная деталь не очень существенна, то это не всегда так. Конечно, рассматривая симметричные тела вроде сферы или куба, вы не заметите особой разницы при изменении направления оси. Но вот, например, для текстов смена направления оси может оказаться весьма печальным преобразованием. В самом деле, пусть на параллелепипеде, моделирующем стену, имеется некоторая надпись, скажем, VRML. Тогда при изменении системы координат мы увидим следующее:

Иными словами, если раньше текст был на “передней” грани, то теперь он оказывается “сзади” стены (чтобы увидеть текст, мы ее “обошли”) и выглядит весьма экзотично. Характерно, что обе приведенные картинки есть отображения двумя разными просмотрщиками (с разными системами координат) одного и того же(!) WRL-файла.

Примечания.

1. Обратите также внимание на разное расположение текста по координате Y.

2. Если вы думаете, что это все неприятности, которые происходят в виртуальных мирах с текстами, то вы посмотрели не все примеры из части 1. Посетите виртуальный Мавзолей [4] и обратите внимание, как выглядит надпись над входом. И это при том, что изображение заведомо рассматривается в “родном” ПО!

Договоримся, что во всех наших примерах мы будем пользоваться системой координат, принятой в версии 2.0.

Правила описания VRML-сцен

4. Граф сцены: узлы и поля (объектная модель). Теперь, когда мы ознакомились с наиболее общими основополагающими принципами языка VRML, перейдем к изучению его конкретных конструкций. Виртуальный мир в терминологии языка называется сценой, которая представляется в виде графа из отдельных объектов, называемых узлами (node). Узлы образуют четкую структуру, которую в информатике принято называть деревом.

Рассмотрим некоторый фрагмент VRML-программы, являющийся частью одного из наших примеров, которые мы будем сегодня рассматривать. Он описывает куб, стенки которого имеют степень прозрачности 0.7 (см. пример 6.1.2).

Фактически пока мы учимся разбираться в “чужом” VRML-тексте, что всегда полезно, прежде чем начинать что-либо писать самому.

Итак, соответствующий фрагмент программы имеет следующий вид.

Первое, что в записи бросается в глаза: отдельные конструкции языка заключаются в фигурные скобки, причем скобки могут быть вложенными. Для того чтобы не запутаться в многочисленных скобках, принято использовать отступы (одинаковые для одноуровневых скобок) и комментарии со словом end, которые показывают, где какая структура заканчивается. Разумеется, все эти “дополнительные к скобкам” приемы применены исключительно для удобства чтения человеком и на изображение объемных объектов на экране никак не влияют.

Примечание. Указанные приемы позволяют писать VRML-текст с меньшим числом ошибок и, если ошибка все же допущена, быстрее ее найти. Это очень важно, поскольку программное обеспечение по отображению 3D-миров нередко просто игнорирует ошибочные конструкции или выполняет их “как-то по-своему”, а когда встроенная диагностика все же предусмотрена, она очень малоинформативна и неудобна…

Далее обратим внимание на слова, написанные с заглавной буквы (для удобства читателей они выделены жирным шрифтом), — это есть не что иное, как узлы (объекты) сцены. Проследив за расстановкой скобок, можно построить дерево узлов, приведенное на рисунке, которое и является графом некоторого фрагмента сцены. В частности, видно, что объект Shape (по-русски “фигура”, “образ”) включает в себя два объекта — Appearance (“внешность”, “наружность”) и Box (изначально “коробка”, но в компьютерной терминологии изображение на экране прямоугольника или параллелепипеда). Последний и есть собственно куб, а информация из первого позволяет куб нарисовать требуемым образом. Узел Appearance, в свою очередь, включает в себя еще один узел (“подузел”) Material, описывающий физические свойства виртуального материала, из которого состоит куб. В нашем случае материал обладает некоторым свойством прозрачности (transparency).

Как мы видим, узлы VRML неодинаковы по смыслу, например, Box и Appearance явно имеют различное предназначение. К сожалению, единая общепринятая классификация узлов как-то не сложилась, поэтому здесь мы не будем обсуждать этот вопрос.

Очень важно подчеркнуть, что с теоретической точки зрения конструкции Box, Material и другие надо называть типом узлов, а не именем! Дело в том, что если кубов будет несколько, то все они будут описываться узлом Box. Что касается уникального имени для объекта, то его в VRML также можно назначить некоторому узлу, но делать это не обязательно. О том, как выглядит синтаксис именования узлов и где он необходим, показано в примере 6.1.5.

В анализируемом фрагменте имеются еще слова, начинающиеся со строчной буквы (надеюсь, читатели помнят о важности регистра?), — appearance, material, transparency и geometry. Это поля (fields), которые являются составными частями объекта и предназначены для хранения в той или иной форме информации об устройстве объекта. Поля в VRML бывают разные, поэтому имеет смысл рассмотреть их подробнее.

Начнем “с конца”: наиболее понятным является поле transparency, имеющее значение 0.7 и принадлежащее объекту типа Material. Это пример самой простой разновидности полей — те, в которых хранятся конкретные значения. Не следует только думать, что это всегда числовое значение и что оно обязательно одно. В частности, поле может являться координатами точки, а значит, содержать в себе 3 вещественных числа (аналогичное свойство имеет место и для полей, хранящих цвет); существуют даже поля для набора координат. Кроме того, специальные поля могут хранить картинку, время и некоторые другие типы данных 3. Полный список типов полей можно посмотреть, например, в [3].

Очень важно для нас сейчас, что существуют такие разновидности полей, в которых хранится ссылка на следующий узел (узлы). Именно такой тип имеют поля appearance, material и geometry; с их помощью происходит “соединение” узлов между собой. Таким образом, неуклюжая запись Shape {appearance Appearance 4 вдруг неожиданно обретает весьма важный структурный смысл. Ее следует понимать так: объект типа Shape имеет поле appearance, с помощью которого к нему присоединен объект типа Appearance, описывающий особенности изображения всего объекта. Рекомендуем читателям самостоятельно поупражняться в расшифровке структуры изучаемого фрагмента, используя приведенный ниже рисунок.

Примечание. Поля с префиксом SF хранят только одно значение. Если же указан префикс MF, то такое поле может вмещать в себя несколько значений (аналог массива). Например, поле SFNode ссылается на один узел, а MFNode способно хранить ссылки на несколько; аналогично SFFloat хранит единственное вещественное значение, а MFFloat — целый набор таких значений.

Описанный механизм “стыковки” узлов между собой через посредство полей ведет к тому, что узлы в VRML сочетаются по строго определенным правилам, например, узел типа Material нельзя “присоединить напрямую” к узлу типа Shape (там нет для этого соответствующего поля). Причина такой избирательности, по-видимому, заключается в необходимости максимально быстрого разбора текста программы интерпретатором VRML.

Анализ приведенного рисунка, возможно, вызвал у внимательных читателей некоторое недоумение: узел Box пуст, неужели у него совсем нет полей? Конечно, есть, но тут действует еще одно важное правило, о котором мы еще не успели сказать. Любое поле объекта в VRML обязательно имеет значение по умолчанию, причем оно не обязательно нулевое или пустое. Например, стороны объекта Box по умолчанию устанавливаются равными 2 (от –1 до 1, начало координат — в центре). Если нас устраивает значение по умолчанию, то в описании соответствующее поле можно пропускать, значительно сокращая запись. Между прочим, и материал бывает не только прозрачным: объекты имеют цвет (точнее, даже несколько, в частности, рассеиваемый или цвет свечения), светимость и некоторые другие характеристики. Просто в нашем примере “задействовано” только одно свойство, поэтому все остальные пропущены и получат значения по умолчанию.

Таким образом, мы полностью разобрались в том, каким образом описывается дерево сцены. Возможно, кому-то такой способ показался утомительным. Им можно посоветовать воспользоваться программным обеспечением более высокого уровня, например, White Dune [5], о котором уже рассказывалось в части 1. Одно из ценных свойств этого ПО состоит в том, что всю “разметку” оно генерирует самостоятельно, а вам остается только в диалоге ввести значения параметров в соответствующие поля (в нашем случае — в поле transparency).

На следующем рисунке изображен фрагмент окна программы после нажатия на кнопку с изображением кубика. Разобранный нами ранее фрагмент в левой части рисунка выделен рамочкой. Видна четкая иерархия объектов, а также в правой части поля для выделенного объекта Shape их узлы-значения Appearance и Box. Если сохранить проект, то в полученном текстовом файле вы легко найдете соответствующий фрагмент, правда, без комментариев после закрывающихся скобок.

Мы не будем сейчас спорить, какой способ лучше — написание текста в Блокноте или автоматическая генерация VRML с помощью специализированной программы-редактора. Очевидно, что тем, кого интересует только результат, последний подход вполне подойдет. Но если читатели хотят, следуя заголовку статьи, понимать VRML и иметь возможность его корректировать и улучшать после универсального редактора, то не обойтись и без “ручного” набора. Оптимальное решение, как всегда, посередине: надо понимать VRML-текст и уметь его править, но какие-то рутинные операции вполне можно выполнять и в редакторе. Тем не менее на стадии первоначального понимания, на которой мы с вами находимся сейчас, лучше пока к помощи редактора не прибегать.

И еще одно замечание методического характера. Наличие иерархической структуры в виде дерева и свойств у его узлов определенно указывают на объектно-ориентированный характер VRML. И это действительно хороший пример ОО описания. Более того, автор рискует утверждать, что детальный (в духе того, как это делается в данной статье) разбор 2–3 примеров на VRML дает для понимания объектно-ориентированной модели существенно больше, нежели реализация 20–30 простейших приложений в среде Visual Basic или Delphi. Все дело в том, что хотя построение библиотеки визуальных компонентов и немыслимо без ОО подхода, но это ощущается лишь при изучении ее внутреннего устройства или при создании собственного компонента, но не в моменты написания обработчиков кнопок из 2–3 операторов. Полностью избавляя нас от необходимости набирать описания, характерные для ООП, современные системы программирования скрывают от нас его сущность, а это едва ли полезно для обучения. Мы даже порекомендовали бы тем читателям, которые интересуются вопросами ООП, после полного прочтения данной публикации составить для себя полную модель узла VRML как объекта (при этом можно дополнительно посмотреть статьи [2] и [3]).

5. Реализация динамических эффектов. Для придания трехмерным сценам каких-либо динамических эффектов используются специальные поля, которые в VRML 2.0 называются exposedField. В переводе с английского exposed означает “открытый”, “видимый”, но по смыслу наиболее хорошо к данному термину, наверное, подходит имеющийся в словаре вариант “подвергаемый воздействию”. Данные поля могут изменять свои значения под влиянием происходящих в виртуальном мире событий. Под событиями подразумевается передача информации от одного объекта другому.

Узел получает сообщения типа eventIn, которые обычно влекут за собой изменения его состояния; события такого рода обозначаются set_X (set_color, set_position). Отправляемые узлом сообщения обычно содержат информацию об изменении его состояния (color_changed, position_changed) и имеют тип eventOut.

Поле exposedField X эквивалентно следующей конструкции:

eventIn set_X

field X

eventOut X_changed,

где field — это само поле, в котором хранится значение, изменяемое по eventIn; происходящее изменение сопровождается инициацией события eventOut. Более подробно о конкретных событиях и их цепочках написано в примере 6.1.5.

Примечание. Интересно подчеркнуть, что похожую структуру (поле, метод чтения и метод записи) имеют свойства объектов в Delphi [6]. Например, когда вы пишете X.Color:=clRed, пытаясь сделать объект X красным, то внутри системы происходит не присвоение, а вызов метода SetColor(clRed), который не просто помещает необходимое значение во внутреннее поле FColor, но и “перекрашивает” объект.

Динамические эффекты создаются при помощи двух типов специализированных узлов: интерполяторов и сенсоров. Они объединяются в некоторый маршрут, по которому передается сообщение.

Начало цепочке событий дает какой-нибудь сенсор. Существуют различные типы сенсоров; большая часть этих узлов реагирует на воздействие на объект при помощи мыши. TouchSensor реагирует на “прикосновение”, т.е. щелчок кнопки. CylinderSensor, PlaneSensor и SphereSensor срабатывают при попытке изменения положения объекта и носят обобщенное название DragSensor. CylinderSensor срабатывает при попытке повернуть объект вокруг оси; такой сенсор применим для моделирования колеса из “Поля чудес”. В отличие от CylinderSensor SphereSensor реагирует на попытку повернуть объект в любом направлении. Как уже понятно, PlaneSensor активируется при попытке линейного перемещения объекта. Узлы ProximitySensor и VisibilitySensor генерируют сообщения о том, что виртуальная камера приблизилась к объекту на заданное расстояние или объект попал в ее поле зрения. TimeSensor представляет собой часовой механизм, отсчитывающий время до генерации события. С одним узлом может быть ассоциировано несколько сенсоров, сообщения которых обрабатываются по очереди.

Сообщение от сенсора передается интерполятору. Такое название для этого класса узлов выбрано потому, что автор VRML-сцены задает любое изменение объекта в виде нескольких промежуточных стадий, а плавным преобразование объекта становится при помощи интерполяции этих дискретных положений. Узел PositionInterpolator способен переместить объект, а OrientatonInterpolator его повернуть, но это только простейшие возможности. При помощи ScalarInterpolator можно как угодно увеличить или уменьшить любой выражаемый численно параметр: радиус, длину ребра все, что хотите. Объект можно перекрасить при помощи ColorInterpolator. Если это многогранник, то CoordinateInterpolator “перекособочит” его, а если объект сферического происхождения, то NormalInterpolator превратит его в мяч для регби или в куриное яйцо.

Для установки порядка передачи сообщений между объектами в VRML имеется директива ROUTE.
В конце файла помещаются строки следующего вида:

ROUTE <Имя узла>.<Имя исходящего события> TO <Имя узла>.<Имя входящего события>

Посмотреть, как выглядит запись конкретных операторов, можно в примере 6.1.5.

Примеры

Вот, наконец, и вся основная теория. А теперь перейдем к примерам.

6.1.1. Простейший куб. Чтобы просто получить на экране куб (аналог первой задачи для начинающего “Hello, world!” в обычном программировании), достаточно следующей простейшей программы.

#VRML V2.0 utf8
Shape{
geometry Box{ }
}

На экране появится яркий светлый куб. Как уже говорилось выше, размер каждой из его сторон равен 2.0, но проверить это на практике невозможно, поскольку кроме куба на экране нет ничего (не с чем сравнить!).

6.1.2. Добавим прозрачности. Поскольку конечная цель состоит в построении сечения, необходимо сделать куб прозрачным, в противном случае сечение внутри куба увидеть не удастся. Сечением займемся позднее, а пока листинг для прозрачного куба. Он детально разобран в разделе 4, поэтому в комментариях не нуждается.

#VRML V2.0 utf8
Shape
{appearance Appearance
{material Material
{transparency 0.7}
} # end Appearance
geometry Box{ }
} # end Shape

Наш куб на экране станет бледнее, поскольку теперь его грани стали прозрачными.

Примечание. В некоторых просмотрщиках прозрачный куб не виден (сам он не светится, а об источниках света мы пока не позаботились), поэтому необходимо включить режим подсветки. К сожалению, универсальную рекомендацию, как это сделать, здесь дать невозможно. В крайнем случае допишите строчку, описывающую источник света, как это делается в 6.1.4.

6.1.3. Построим сечение. Это более трудный с точки зрения языка VRML этап. Листинг, который надо добавить к предыдущему, выглядит следующим образом.

# Этот текст надо добавить к примеру 6.1.2!
Shape
{appearance Appearance
{material Material
{diffuseColor 1 0 0}
} # end Appearance
geometry IndexedFaceSet
{coord Coordinate
{point [-1 1 -1, 1 1 -1, 1 -1 1, -1 -1 1]}
coordIndex [0 1 2 3 -1]
solid FALSE
} # end IndexedFaceSet
} # end Shape

Данный текст предназначен для рисования сечения куба красного цвета, которое приведено на рисунке.

Рассмотрим описание сечения подробнее. Узел Material определяет диффузный (т.е. рассеивающийся при отражении) красный цвет (о кодировании цветов см. раздел 2). Узел IndexedFaceSet задает в пространстве плоскость, определенную соединением характерных точек. Их координаты, начиная с левой верхней вершины, помещены в поле point узле Coordinate. В данной записи стоит обратить внимание на то, как записаны “тройки” координат: они заключены в квадратные скобки, что подчеркивает множественность значений для данного типа. Далее следует поле coordIndex, которое задает порядок соединения точек. Важно заметить, что номера точек начинаются с 0, а брать их из предшествующего списка по порядку, как в данном примере, совсем необязательно. Последовательность завершается заведомо неиспользуемым значением –1; после него можно добавлять и другие плоскости, правда, в нашем примере это не требуется.

Примечания. 1. Те, кто знаком с языками программирования, несомненно, узнали в полях coord и coordIndex массив (его эквивалентное описание на Паскале выглядит так: ARRAY [0..N–1] OF RECORD X, Y, Z: REAL END) и его индекс. 2. То, что сечение на экране совместится с требуемыми гранями куба, подтвердит правильность наших знаний о координатах его вершин по умолчанию.

Наконец, установка значения FALSE в последнем поле узла под названием solid (“твердый”, “сплошной”) облегчает “закраску” плоскости: если установить его в TRUE, то сечение станет твердым и придется принимать дополнительные меры по окраске противоположной его стороны.

6.1.4. Позаботимся об освещении. Как уже было сказано в примере 6.1.2, в некоторых виртуальных мирах может оказаться слишком темно, поэтому стоит завести собственный источник освещения. Добавить его совсем просто, дописав к тексту, который у нас получился в примере 6.1.3, всего одну строку:

# Эту строку надо добавить к примеру 6.1.3!

SpotLight {location 0 0 3}

В результате в точке (0, 0, 3) появится небольшой прожектор, который не даст нашему творению исчезнуть с экрана, даже если пользователь, который его рассматривает, случайно выключит свет в виртуальном мире.

6.1.5. Обеспечим хороший осмотр. Конечно, передвигаясь в VRML-мире с помощью мыши или клавиатуры, можно обойти наш кубик вокруг и все рассмотреть. Но есть и более приятная возможность: заставить кубик поворачиваться [2]. Если вы еще не устали, попробуем это осуществить.

Полный листинг примера приведен ниже. Чтобы сэкономить время, можно дополнить пример 6.1.4 тремя вставками текста, которые выделены жирным шрифтом.

#VRML V2.0 utf8
DEF Spin_Timer TimeSensor
  {cycleInterval 4
    loop TRUE
    } # end TimeSensor

DEF Spin_Interpolator OrientationInterpolator
     {key [0, 0.5, 1.0]
       keyValue [0 1 0 0, 0 1 0 3.14159,
                                0 1 0 6.2831]
      } # end OrientationInterpolator
DEF Rotating_Cube Transform
    {children
        [
# 6.1.3
       Shape
         {appearance Appearance
            {material Material
                {transparency 0.7}
          } # end Appearance
        geometry Box{ }
       } # end Shape
  Shape
      {appearance Appearance
         {material Material
            {diffuseColor 1 0 0}
         } # end Appearance
     geometry IndexedFaceSet
       {coord Coordinate
          {point [-1 1 -1, 1 1 -1, 1 -1 1,
                            -1 -1 1]}
           coordIndex [0 1 2 3 -1]
           solid FALSE
      } # end IndexedFaceSet
    } # end Shape
  # end 6.1.3
        ] # end children
     } # end Tranform

# 6.1.4
SpotLight {location 0 0 3}
# end 6.1.4
ROUTE Spin_Timer.fraction_changed TO Spin_Interpolator.set_fraction
ROUTE Spin_Interpolator.value_changed TO Rotating_Cube.set_rotation

Рассмотрим полученный листинг подробнее.

Первый из добавляемых объектов TimeSensor обеспечивает периодическую (благодаря значению поля loop 5) подачу сигналов для очередного поворота. Скорость вращения можно регулировать значением поля cycleInterval.

Примечание. Конструкция DEF Spin_Timer TimeSensor демонстрирует присвоение создаваемому узлу типа TimeSensor выбранного нами имени Spin_Timer. Наличие имени узла в VRML, как мы знаем, не является обязательным. И хотя до сих пор мы обходились без имен объектов, в данном случае они совершенно необходимы для последующего описания маршрута передачи информации между узлами.

Срабатывание таймера передается следующему объекту OrientationInterpolator. Его поле keyValue представляет собой массив возможных значений изменяемого (интерполируемого) параметра. В данном случае этот параметр есть набор значений для поля rotation узла Transform (т.е. начальной установки нашего куба с сечением). Каждый поворот задается четверкой чисел: первые три выбирают оси вращения (набор 0 1 0, который используется, описывает поворот вокруг “второй” оси Y) и угол поворота в радианах (0, и 2). Использование интерполяции позволяет задать всего три положения объекта начальное, конечное и одно промежуточное. Поле key определяет временныRе характеристики вращения: в нашем случае переход от одного положения к другому происходит равномерно.

Примечание. Очень советую загрузить данный VRML-файл в программу White Dune и выделить рассматриваемый узел: в нижней части окна вы увидите графическое отображение процесса движения и сможете его “проиграть”.

Интерполятор будет воздействовать на узел Rotating_Cube, в котором путем “подключения” к полю children (“дети” типичный образчик терминологии в ООП-наследовании!) сгруппированы уже знакомые нам два узла типа Shape с кубом и его сечением. Здесь все осталось по-старому, не забудьте только добавить закрывающие скобки в конце описания объединенного узла.

Наконец, в последних строках файла строится маршрут (ROUTINE) передачи сообщений для описываемого динамического события. Отчетливо просматривается цепочка узлов Spin_Timer ® Spin_Interpolator ® Rotating_Cube (вот где потребовались данные нами ранее имена узлов!), передающая изменения от одного события к другому.

Как видно из приведенного выше описания, усилий для создания вращения потребовалось много, но они того стоили!

6.2. Снеговик. Попробуем создать более сложный объемный объект. Поскольку данные строки пишутся в канун Нового года, тема нашлась сразу это снеговик. Симпатичный и вместе с тем простой с геометрической точки зрения объект.

Размеры основных (сферических) частей снеговика и его итоговый вид представлены на рисунке. Посмотрим, как это выглядит на VRML.

6.2.1. Самое первое решение. На первом этапе создадим изображение, руководствуясь исключительно простотой действий и не обращая пока внимание на оптимальность результатов. “Платой” за такой подход всегда является хотя и легко читаемая, но чрезвычайно громоздкая программа. Тем не менее в обучении такой подход часто бывает оправдан, поскольку можно быстро увидеть результат. А оптимизацией займемся в следующем разделе.

Полный листинг программы приводится ниже. Он очень большой, но это не должно вас пугать: он состоит из нескольких несложных, относительно независимых фрагментов с типовым содержимым. А применение копирования с помощью буфера обмена позволяет получить требуемый текст совсем быстро.

#VRML V2.0 utf8
Transform # bottom
    {children
         [Shape 
                {appearance Appearance
                      {material Material
                            {diffuseColor 1 1 1
                              emissiveColor 0.5 0.5 0.5
                       } # end Material
                 }# end Appearance
                geometry Sphere { }
          } # Shape
      ] # end children
 } # end Transform
Transform # middle
    {translation 0 1.7 0
      children 
          [Shape
              {appearance Appearance
                    {material Material
                          {diffuseColor 1 1 1
                            emissiveColor 0.5 0.5 0.5
                    } # end Material
              }# end Appearance
           geometry Sphere {radius 0.7}
       } # Shape
        ] # end children
   } # end Transform
Transform # top
    {translation 0 2.8 0
       children
             [Shape 
                  {appearance Appearance
                       {material Material
                           {diffuseColor 1 1 1
                             emissiveColor 0.5 0.5 0.5
                       } # end Material
                }# end Appearance
            geometry Sphere {radius 0.4}
        } # Shape
      ] # end children
    } # end Transform
Transform # right
   {translation 0.9 1.7 0
      children 
        [Shape 
               {appearance Appearance
                      {material Material
                            {diffuseColor 1 1 1
                              emissiveColor 0.5 0.5 0.5
                       } # end Material
               }# end Appearance
            geometry Sphere {radius 0.3}
        } # Shape
       ] # end children
     } # end Transform
Transform # left
    {translation -0.9 1.7 0
       children 
             [Shape 
                 {appearance Appearance
                     {material Material
                        {diffuseColor 1 1 1
                         emissiveColor 0.5 0.5 0.5
                      } # end Material
                 }# end Appearance
             geometry Sphere {radius 0.3}
          } # Shape
       ] # end children
     } # end Transform
Transform # red nose
      {rotation 1 0 0 1.57
        translation 0 2.65 0.4
         children
               [Shape 
                   {appearance Appearance
                        {material Material
                            {diffuseColor 1 0 0}
                   } # end Appearance
                geometry Cone
                   {bottomRadius 0.1
                      height 0.5
                      bottom FALSE
                    } # end Cone
                } # Shape
          ] # end children
     } # end Transform
Transform # right eye
     {rotation 1 0 0 1.57
       translation 0.1 2.8 0.38
        children 
          [Shape 
               {appearance Appearance
                   {material Material
                      {diffuseColor 0.1 0.1 0.1}
                } # end Appearance
               geometry Cylinder
               {height 0.02
                 radius 0.05
                }# end Cylinder
           } # end Shape
         ] # end children
      } # end Transform
Transform # left eye
     {rotation 1 0 0 1.57
        translation -0.1 2.8 0.38
        children 
         [Shape 
           {appearance Appearance
            {material Material
              {diffuseColor 0.1 0.1 0.1}
            } # end Appearance
            geometry Cylinder
             {height 0.02
               radius 0.05
            }# end Cylinder
         } # end Shape
      ] # end children
   } # end Transform

Нетрудно видеть, что снеговик состоит из 5 сфер. В соответствии с этим в листинге 5 из 8 фрагментов очень похожи. Мы рассмотрим только один из них; поскольку первая (нижняя) сфера не является типичной (она имеет центр в начале координат и единичный радиус см. рисунок), мы обратимся ко второй. Ее описание легко найти в тексте по комментарию middle.

Фрагмент, описывающий “средний” ком снеговика, начинается с узла типа Transform. Его назначение состоит в задании геометрических характеристик изображения объекта-потомка, в частности, наиболее часто используется его поле translation. В нашем случае это поле содержит тройку чисел
0 1.7 0, что является набором координат для центра рассматриваемой сферы. Таким способом обеспечивается позиционирование нашей сферы в виртуальном пространстве.

К описанному выше узлу через поле children подсоединяется стандартный узел типа Shape, описывающий уже знакомым нам образом сферу. Отметим только, что, кроме диффузного белого цвета снеговика, мы добавили ему еще некоторую “подсветку изнутри” с помощью поля emissiveColor. Желающие могут проверить, насколько хуже будет выглядеть снеговик без этой в общем-то не очень логичной меры.

Все остальные четыре сферических комка виртуального снега строятся аналогично. Разумеется, меняется их радиус и положение, которое нетрудно вычислить, используя имеющиеся на чертеже размеры.

Гораздо больший интерес представляют конический красный (VRML-цвет 1 0 0) нос, имитирующий традиционную морковку, и цилиндрические глаза.

Обратимся к описанию носа. Поскольку по умолчанию конус строится вершиной вверх, а нос снеговика требуется по понятным причинам направить “вперед”, необходимо произвести поворот конуса. Поле rotation обеспечивает это. Как мы уже видели в примере 6.1.5, поворот характеризуется набором из четырех чисел: тройка 1 0 0 описывает поворот вокруг оси X на 1.57 радиан, т.е. на 90°.

С аналогичным поворотом строятся и оба цилиндрических глаза черного (0.1 0.1 0.1) цвета.

Автор надеется, что опыт, полученный читателями при разборе и реализации предыдущих примеров, позволит легко “прочитать” предлагаемую VRML-программу.

6.2.2. Займемся оптимизацией. Анализируя программу, вы, вероятно, обратили внимание на то, что образующие снеговика сферы описываются практически идентичным текстом, отличающимся лишь числовыми значениями, которые задают радиус и положение центра сферы. Возникает естественное желание как-то оформить общую часть и затем обращаться к ней, указывая конкретные значения параметров.

Для таких целей в VRML предусмотрен специальный механизм прототипы. При создании прототипа мы соберем в него общее описание сферического объекта, а затем будем ссылаться на него, указывая конкретные числовые значения параметров.

Давайте удалим текст VRML-программы, соответствующий пяти сферам, а вместо него наберем следующий листинг.

Примечание. Одно из описаний можно оставить и из него сформировать прототип.

PROTO KOM[field SFVec3f p 0 0 0
                       field SFFloat r 1
                     ]
{
Transform
       {translation IS p
         children
           [Shape
              {appearance Appearance
                   {material Material
                       {diffuseColor 1 1 1
                         emissiveColor 0.5 0.5 0.5
                     } # end Material
               } # end Appearance
         geometry Sphere
     {radius IS r}
         } # end Shape
       ] # end children
     } # end Transform
} # end KOM
KOM {}
KOM {p 0 1.7 0 r 0.7}
KOM {p 0 2.8 0 r 0.4}
KOM {p 0.9 1.7 0 r 0.3}
KOM {p -0.9 1.7 0 r 0.3}

Описание прототипа открывается служебным словом PROTO, за которым следует имя прототипа. В нашем случае это KOM, набранное обязательно (!) латинскими буквами. Далее в квадратных скобках описываются параметры прототипа, которые можно будет в дальнейшем задавать при обращении к нему.

Описание параметров-полей заслуживает того, чтобы познакомиться с ним поподробнее. После стандартного ключевого слова field (как вы, наверное, помните, field и означает поле) следует его тип, имя и значение поля по умолчанию. В нашем случае имеется два поля позиция центра сферы p и ее радиус r. Первый имеет тип SFVec3f, т.е. одно значение трехмерного вектора, которое, естественно, задается тремя вещественными числами. Радиус сферы имеет (единственное) вещественное значение SFFloat. Указанные значения по умолчанию традиционны для VRML, кроме того, они полностью соответствуют нижнему кому снеговика.

Далее следует (заключенный в фигурные скобки) стандартный текст описания сферы из виртуального снега, уже хорошо нам знакомый. Единственное отличие заключается в написании полей, в которые подставляются параметры. Например, для центра сферы это выглядит так:

translation IS p

— где IS это служебное слово, показывающее необходимость подстановки значения параметра p. По смыслу эта запись означает, что в поле translation необходимо поместить то значение, которое при обращении будет указано для параметра p. А само обращение к прототипу KOM, показанное в конце фрагмента, настолько очевидно, что особых комментариев не требует.

Примечание. Те из читателей, кто знаком с языком программирования вроде Паскаля, конечно, узнали в описанном механизме обращение к процедуре с параметрами. Все-таки как полезно уметь программировать при работе на компьютере! Это может пригодиться в самых неожиданных местах.

В качестве упражнения можем порекомендовать читателям оформить описание глаза снеговика в виде прототипа. Обратите внимание на тот факт, что для глаза радиус не является внешним параметром (в отличие от комьев оба глаза одинаковы), поэтому его стоит по-прежнему оставить в виде конкретной константы.

Что же мы получили в итоге всей этой оптимизации с прототипами? Программу, которая в несколько раз короче, и знание механизма, который позволяет компактно описывать однотипные объекты. Эти объекты даже можно вынести в отдельный файл; тогда они будут называться EXTERNPROTO. Об использовании данной возможности языка VRML можно почитать в [7].

И в заключение еще одно замечание по поводу технологий. Мы можем сделать снеговика, используя готовые технологии, например, многократно упоминавшуюся программу White Dune. При этом даже вообще не обязательно знать правила записи VRML. Но результат, который получится, будет эквивалентен листингу в 6.2.1. Тем не менее получение эффективного результата требует понимания сущности и правил устройства технологии, что в очередной раз подтвердил пример 6.2.2.

6.3. Еще примеры: короткие, но интересные. В заключение приведем еще пару коротеньких листингов, демонстрирующих некоторые интересные возможности VRML.

6.3.1. Пример гиперссылки. Обязательно хочется показать, что VRML, как и HTML, поддерживает гиперссылки. Вот несложный пример, не нуждающийся в особых комментариях.

#VRML V2.0 utf8
Anchor
    {url "http://inf.1september.ru"
       children
         [Shape
             {geometry Text {string
              "http://inf.1september.ru"}
            }
         ]
}

А вот как это выглядит:

6.3.2. Да будет звук! И еще один прекрасный пример компактной VRML-программы [3], описывающей шарик, который издает звук, когда на него наталкивается наблюдатель.

#VRML V2.0 utf8
DEF SphereWithSound Collision
    {children
          [Shape
                {geometry Sphere { }
                }
             Sound
              {source DEF BoomSound AudioClip {url
                  "boom.wav"}
               }
          ]
    }
ROUTE SphereWithSound.collideTime TO BoomSound.startTime

Разумеется, в том же каталоге должен лежать звуковой файл “boom.wav”.

Как это можно использовать

Приведенные в публикации материалы могут быть полезны не только учителям информатики при рассказе о современных 3D-технологиях компьютерной графики. Автор надеется, что изложенная теория и разобранные примеры позволят творческим учителям организовать своих учеников на изготовление простейших электронных ресурсов по стереометрии, астрономии, химии, физике и другим школьным предметам. Одна из целей части 1 публикации как раз и состояла в том, чтобы показом ярких возможностей VRML разбудить фантазию. А в части 2 публикации кратко познакомить с тем, как подобные материалы можно разрабатывать.

Не сомневаюсь, что при наличии интереса читатели смогут своими силами создать действительно наглядные, красивые и практически полезные VRML-объекты и даже простейшие виртуальные миры (вроде музейных залов, на стенах которых вместо картин развешены портреты ученых или математические формулы, или “фотографии” экзотической планеты). Желаю всяческих творческих успехов на данном поприще!

Ссылки

1. Teaching Teaching & Understanding Understanding. http://www.daimi.au.dk/~brabrand/short-film/.

2. Авдуевский А. По следам газонокосильщика. LAN, 1997, № 1. http://www.osp.ru/lan/1997/01/132452/.

3. Медведев А. Реальная Виртуальность. http://www.ixbt.com/peripheral/real-vrml.html.

4. Virtual Mausoleum. http://www.parallelgraphics.com/vrml/lenin/vmas_e.htm.

5. White Dune. http://vrml.cip.ica.uni-stuttgart.de/dune/.

6. Конопка Р. Создание оригинальных компонент в среде Delphi. Киев: НИПФ-ДиаСофт Лтд., 1996, 512 с.

7. Кузин А. VRML шаг за шагом. http://www.firststeps.ru/www/vrml/vrml1.html.


1 Разумеется, версия 2.0 тоже представлена, просто там труднее сосчитать полное количество узлов.

2 В файлах с описанием внешних прототипов сцена, как правило, отсутствует.

3 Существует также особый класс полей exposedField, о которых речь впереди.

4 Мой редактор усиленно рекомендует “удалить повторяющееся слово” .

5 Loop по-английски означает “петля”; в компьютерной терминологии это общепринятое название цикла.

Е.. А.. Еремин,
г. Пермь

TopList