Анимация. Сенсоры, маршруты, интерполяторы

Общие замечания

Под анимацией понимаются не только визуальные проявления (изменение у объектов координат, размера, цвета и т.д.), но и любые другие виды динамического изменения сцены (например, включение звука). И первое, что Вам нужно запомнить - волшебное слово "event", или "событие". Это сообщение о том, что произошло некоторое событие. При этом у каждого event есть свой "timestamp", т.е. пометка времени, когда произошло событие. Эта пометка служит, во-первых, чтобы события обрабатывались в хронологическом порядке, а во-вторых пометки можно обрабатывать скриптами. Как уже говорилось во Введении, у узлов могут быть параметры EventIn (принимают сообщения о событиях), EventOut (посылают сообщения о событиях) и ExposedField (делает и то, и другое, и к тому же имеет некоторое значение).

Если абстрактно задаться вопросом "Что необходимо для того, чтобы создать анимацию?", то неизбежно возникает несколько стадий решения, а именно:

Ответами на каждый из трех вопросов в VRML занимаются соответствующие средства.

Активацией событий занимаются узлы-сенсоры (генерируют EventOut), указанием на конкретный объект занимаются "ROUTE"ы, или "маршруты" (транспортируют EventOut к объекту), изменением объектов занимаются "Interpolator"ы, или "интерполяторы" (обрабатывают EventIn и отправляют через очередной ROUTE объекту новые параметры).

Разберем по порядку всех участников.

Сенсоры

Основное назначение сенсоров - сгенерировать EventOut после срабатывания. Срабатывание сенсора может быть вызвано разными причинами: наступление определенного времени, клик мышкой, наведение курсора, приближение к объекту, столкновение с объектом и т.д.)

К классу сенсоров относятся следующие узлы: Anchor, Collision, CylinderSensor, PlaneSensor, ProximitySensor, SphereSensor, TimeSensor, TouchSensor, VisibilitySensor

Anchor

Описание:

Anchor {
children [ ]
description " "
parameter [ ]
url [ ]
bboxCenter 0 0 0
bboxSize -1 -1 -1
}

Несмотря на отсутствие в явном виде раздела EventOut, узел Anchor относится к сенсорам, поскольку EventOut все-таки генерируется, только маршрут для сообщения строго определен и не может быть изменен. Результатом является обращение к документу, указанному в разделе url. Это может быть не только VRML сцена, но и (гипер)текстовый документ и т.д.

Пощелкайте мышкой по объектам.

Просмотр.

Кроме того, работают (по крайней мере ДОЛЖНЫ работать) anchor'ы в виде url [имя_файла#viewpoint для VRML файлов и имя_файла#name для HTML файлов. Напомню, что Anchor является grouping узлом, поэтому ссылкой становятся все объекты в пределах раздела children.

Параметр description - чисто описательный. При наведении курсора на объект с anchor'ом в статус-лайне обычно отображается содержимое description.

В дополнение к параметру url есть параметр parameter (такой вот каламбурчик...%), это механизм, аналогичный HTML, для отображения файла в опредленном фрейме. Если фрейм не указан, создается новое окно.

Вот предыдущий пример, только изменено содержимое parameter [ ] Просмотр. Указанный фрейм new не существует поэтому создается новое окно.

Параметр bbox (или bounding box) - это такая штука, предназначенная для ускорения рендеринга. При этом children узлы ищутся уже не по всему пространству а в пределах этого самого ящика (bounding box) с размерами bboxSize и центром в bboxCenter.

НО !!! необходимо, чтобы children узлы ДЕЙСТВИТЕЛЬНО попадали в пределы bounding box, иначе результат не определен. Поэтому ВЫВОД:
проще никогда не связываться с bbox и оставить ее по умолчанию бесконечно большой (bboxSize -1 -1 -1)

Collision

Описание:

Collision {
children [ ]
collide TRUE
bboxCenter 0 0 0
bboxSize -1 -1 -1
proxy NULL
eventOut collideTime
}

Узел Collision выполняет две задачи: во-первых, регистрирует факт столкновения (и время этого столкновения) аватара (т.е. Вас) с объектом, указанным в разделе children, а во-вторых регулирует, пройдете ли Вы СКВОЗЬ объект (collide TRUE) или нет (collide FALSE). В момент столкновения генерируется eventOut collideTime, что позволяет создать последовательность последующих событий. (Учтите, что невозможно организовать столкновения с IndexedLineSet, PointSet, Text.)

О размерах аватара, который, собственно, сталкивается, смотрите в разделе NavigationInfo

Если в Вашем VRML файле нет ни одного узла Collision, то "проницаемость" объектов регулирует броузер. Наверняка Вы видели в настройках броузера что-нибудь вроде collision ON/OFF, collider Auto/Always/Never и т.д.

Про bboxCenter и bboxSize читайте в разделе Anchor.

Параметр proxy - это очень полезная вещь для ускорения рендеринга. Если в разделе у Вас не NULL, а какой-нибудь ОБЪЕКТ, то он не изображается в сцене (невидим), НО !!! столкновение на самом деле будет регистрироваться с ним, т.е. объектом, указанным в разделе proxy, а не в разделе children.

К примеру, Вам необходимо сделать зарегистрировать столкновение с объектом сложной формы, что-нибудь вроде:

Просмотр.

Зачем напрягать броузер расчетом положения аватара относительно всех этих граней? Просто прописываете в разделе proxy СФЕРУ чуть большего радиуса, чем "иглы" звезды и все.

Просмотр. Не забудьте выставить у броузера collide ON !

ВЫВОД: как и для всех узлов, значения которых может проигнорировать броузер (NavigationInfo: avatarSize и headlight ON/OFF), ценность узла Collision резко снижена! Вместо пользуйтесь, например, ProximitySensor !

ProximitySensor

Описание:

ProximitySensor {
center 0 0 0
size 0 0 0
enabled TRUE
eventOut isActive
eventOut position_changed
eventOut orientation_changed
eventOut enterTime
eventOut exitTime
}

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

А когда Вы уже находитесь внутри параллелепипеда генерируются следующие сообщения:

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

Пример. Хотите всегда проезжать на зеленый свет? Нет проблем. Просто приближайтесь к светофору.

Просмотр.

Обратите внимание: три ProximitySensor'а и eventOut каждого из них напрямую меняет значение DirectionLight ON/OFF. По-моему, довольно изящно.

TimeSensor

Описание:

TimeSensor {
cycleInterval 1
enabled TRUE
loop FALSE
startTime 0
stopTime 0
eventOut cycleTime
eventOut fraction_changed
eventOut isActive
eventOut time
}

Исключительно важный узел. В 90 процентах случаев Вы не обойдетесь без него при организации анимации, поскольку именно здесь можно регулировать СКОРОСТЬ протекания процессов.

Обратите внимание, что по умолчанию выставлено enabled TRUE. Это означает, что если Вы не указали в явном виде enabled FALSE (а также не меняли поле startTime 0), то после загрузки сцены TimeSensor сразу начинает генерировать разнообразные eventOut'ы ! Т.е. по умолчанию TimeSensor НЕ ПРИХОДИТСЯ АКТИВИРОВАТЬ, и этим он принципиально отличается от остальных узлов-сенсоров.

НО!!! Не забудьте, что кроме поля enabled, есть поля startTime и stopTime, которые имеют больший приоритет, чем enabled. Но об этом - чуть позднее.

А пока разберемся с остальными полями.

Цикл работы TimeSensor'а может быть либо один (loop FALSE) либо при loop TRUE циклов будет много, (бесконечно, если stopTime меньше, чем startTime, иначе пока не наступит stopTime). Длительность цикла задается полем cycleInterval.

Что же происходит хронологически?

Если startTime=0, то как только TimeSensor активирован (при enabled TRUE - сразу после загрузки, а при enabled FALSE - после получения сообщения TimeSensor.enabled), генерируются следующие eventOut'ы:

eventOut isActive Посылается значение TRUE до тех пор, пока TimeSensor не будет деактивирован

eventOut cycleTime Посылается в момент наступления startTime и в начале каждого последующего цикла при loop TRUE.

eventOut time Посылает абсолютное значение времени, прошедшего с начала цикла.

eventOut fraction_changed В отличие от eventOut time посылает ОТНОСИТЕЛЬНОЕ значение степени завершенности цикла: в начале цикла fraction_changed=0, в конце цикла fraction_changed=1. Это один из наиболее часто используемых eventOut'ов. При loop TRUE почти всегда используется волшебная связка

.fraction_changed - .set_fraction - .value_changed,

где .fraction_changed - это часть от TimeSensor, .set_fraction - часть от интерполятора, .value_changed - часть от узла, у которого изменяются свойства.

Теперь поговорим о startTime и stopTime.

Как известно, в сетевых технологиях счет времени ведется с 00:00:00 1 января 1970 года, и значения полей startTime и stopTime в том числе. Но поскольку эти значения надо указать в секундах (т.е. сколько прошло с 00:00:00 1 января 1970 года), то чтобы пользоваться этими замечательными средствами управления TimeSensor'а не обойтись без маленького скрипта. Если дальнейшее изложение Вам непонятно, читайте раздел Скрипты.

Знаете ли Вы, например, сколько секунд прошло с 00:00:00 1 января 1970 года на момент открытия Вами страницы? А вот сколько:

964597195

Можете сделать пару reload'ов страницы и убедиться, что число обновляется.

Это можно было бы легко использовать для запуска анимации в заданное время. НО !!! надо быть очень осторожным. Самым удобным методом для запуска анимации мог бы быть - getTime(), который должен возвращать количество миллисекунд с 00:00:00 1 января 1970 года. В случае Cosmoplayer и Cortona это действительно так, но MS VRML 2.0 Viewer возвращает количество СЕКУНД, т.е. число в тысячу раз меньше!!!

Вот пример, который работает в случае MS VRML Viewer и ни в чем другом.

Просмотр. Движение шара начинается ровно через 2 секунды после открытия файла и длится 4 секунды.

А вот тот же пример, модифицированный для работы в Cosmoplayer, (хотя в в Кортоне он и в таком виде не работае, почему - хоть убейте не понимаю).

Просмотр. Если Вы сравните код, то увидите, что он отличается только тем, что пришлось перевести миллисекунды в секунды (поделить на 1000),

В общем, как бы то ни было:

ВЫВОД: никогда не связывайтесь с вычислением в явном виде абсолютных значений при измерении времени (time, startTime, stopTime) ! А если Вам нужно запустить анимацию в определенное время, пользуйтесь средствами, работающими с относительными значениями (cycleInterval).

И тут есть два противоположных случая: первый - если после запуска в определенное время далее анимация циклично повторяется (loop TRUE), второй - если после запуска в определенное время анимация должна совершиться ОДИН РАЗ.

Первая задача решается очень легко. Посмотрите пример и поймете сами:

Просмотр. Шарик начнет двигаться через 4 секунды после открытия файла и будет двигаться бесконечно!

Вторая задача несколько сложнее, поскольку нам необходимо получить сигнал set_startTime.

Кто разберется в примере, надеюсь оценит идею.

Просмотр. Шарик начинает двигаться через 4 секунды и движется 5 секунд.

TouchSensor

Описание:

TouchSensor {
enabled TRUE
eventOut hitNormal_changed
eventOut hitPoint_changed
eventOut hitTexCoord_changed
eventOut isActive
eventOut isOver
eventOut touchTime
}

Хороший сенсор, обычно используемый для большей интерактивности: чтобы что-нибудь открылось/закрылось/заработало и т.д. приходится навести курсор или щелкнуть на чем-нибудь мышкой.

Поле у узла всего одно enabled TRUE/FALSE, а сам сенсор привязывается ко всем объектам, объединенным с TouchSensor'ом в одну parent группу.

Работа сенсора начинается с момента наведения курсора мыши (или другого манипулятора) на объект, к которому привязан TouchSensor. При этом начинает генерироваться eventOut isOver TRUE. А кроме того при перемещении курсора над поверхностью объекта генерируются eventOut hitNormal_changed (отслеживается положение вектора нормали к поверхности объекта), eventOut hitPoint_changed (отслеживаются координаты точки на поверхности объекта, над которой находится курсор), eventOut hitTexCoord_changed (отслеживается положение точки на поверхности объекта, над которой находится курсор в координатах texture map).

Оставшиейся два eventOut'a eventOut isActive и eventOut touchTime генерируются при участии кнопки мыши: isActive TRUE генерируется только до тех пор пока Вы держите кнопку мыши НАЖАТОЙ (а после отпускания isActive FALSE), а eventOut touchTime наоборот генерируется КАК ТОЛЬКО Вы ОТПУСКАЕТЕ кнопку мыши.

Наиболее употребительными мне представляются isOver, isActive и touchTime

Просмотр.

VisibilitySensor

Описание:

VisibilitySensor {
center 0 0 0
enabled TRUE
size 0 0 0
eventOut enterTime
eventOut exitTime
eventOut isActive
}

Этот сенсор определяет, находится ли в поле зрения область пространства внутри параллелепипеда с центром в center и размером size. Как только область становится видимой, генерируется eventOut enterTime и isActive становится TRUE. Как только Вы "отвернулись" и не видите содержимое параллелепипеда, генерируется eventOut exitTime и isActive становится FALSE.

Сенсор может быть полезен для оптимизации Вашей сцены. К примеру, можно прекращать часть анимации, который в данный момент не видна.

Просмотр. Если Вы будете медленно "отворачиваться" от шара, можете заметить, что когда шар практически исчез с экрана, он прекращает менять цвет. Останавливая таким способом обработку анимации, можно увеличить число fps.

Dragging Sensors

Название этого подраздела обусловлено тем, что для активирования описываемых здесь сенсоров можно не только кликнуть мышью (нажать кнопку и ОТПУСТИТЬ), а нажать и НЕ ОТПУСКАЯ перемещать. Наверняка Вам знакомо понятие "drag and drop", вот эти сенсоры из такой серии.

PlaneSensor

Описание:

PlaneSensor {
autoOffset TRUE
enabled TRUE
maxPosition -1 -1
minPosition 0 0
offset 0 0 0
eventOut isActive
eventOut trackPoint_changed
eventOut translation_changed
}

Этот сенсор отслеживает перемещения курсора в плоскости с Z=0 локальной системы координат (по умолчанию в плоскости экрана).

Как только над объектом, к которому привязан PlaneSensor, происходит нажатие кнопки мыши, генерируется eventOut isActive. После этого при перемещении курсора при нажатой кнопке мыши отслеживаются текущие значения координат курсора (eventOut trackPoint_changed) и вектора перемещения (eventOut translation_changed).

Поле autoOffset определяет, будут ли суммироваться смещения (autoOffset TRUE) или каждое смещение будет отсчитываться от исходного положения объекта (autoOffset FALSE).

Просмотр. Для обоих шариков проделайте следующее: сдвиньте, отпустите кнопку мыши и попробуйте сдвинуть снова. Тот, что слева начнет двигаться с того места, на котором Вы его бросили (autoOffset TRUE), а тот что слева начнет двигаться с того места, в котором он был изначально (autoOffset FALSE)

Поле enabled TRUE/FALSE разрешает/запрещает работу сенсора.

Пара полей maxPosition и minPosition позволяют организовать двумерное движение в ограниченном регионе (как это было в предыдущем примере) или даже свести движение к одномерному. Просмотр. Если maxPosition меньше, чем minPosition, то движение не ограничивается.

Поле оffset определяет первоначальное смещение, относительно исходного положения объекта, а значит и точку, с которой будет каждый раз начинать движение объект при autoOffset FALSE.

CylinderSensor

Описание:

CylinderSensor {
autoOffset TRUE
diskAngle 0.262
enabled TRUE
maxAngle -1
minAngle 0
offset 0 0 0
eventOut isActive
eventOut rotation_changed
eventOut trackPoint_changed
}

Этот сенсор отслеживает движение курсора мыши в цилиндрической системе координат невидимого цилиндра с осью вращения параллельной локальной оси Y.

Большинство полей (autoOffset, enabled, offset) и eventOut'ов этого сенсора (isActive, trackPoint_changed) такие же, как и у PlaneSensor, где Вы и можете про них прочитать.

Пара полей maxAngle и minAngle аналогична maxPosition и minPosition PlaneSensor. Если maxAngle меньше, чем minAngle, то вращение не ограничивается.

eventOut rotation_changed аналогично eventOut translation_changed PlaneSensor.

Единственное действительно отличительное поле - это diskAngle.

В спецификации предусмотрено два способа описание движения курсора через CylinderSensor. Представьте себе отдельно взятое велосипедное колесо. Если Вы всунете палец между спиц, то сможете вращать колесо бесконечно НЕ ОТРЫВАЯ руку, которая будет описывать конус вращения. А если Вы схватитесь за обод, то для поддержания бесконечного вращения Вам придется постоянно ПЕРЕХВАТЫВАТЬ руку.

Аналогично, в VRML можно хвататься через CylinderSensor за ТОРЕЦ цилиндра или за его БОКОВУЮ СТОРОНУ.

Для регулирования, когда используется какой способ и введен diskAngle. Если угол между bearing вектором и осью цилиндра МЕНЬШЕ diskAngle, то Вы сможете, зацепив мышью цилиндр, вращать его бесконечно (при этом курсор будет описывать на экране круги). Если угол между вектором и осью цилиндра БОЛЬШЕ diskAngle, то придется, провернув немного цилиндр, отпускать кнопку мыши и перетаскивать курсор (при этом курсор на экране будет двигаться дискретно-прямолинейно). {bearing вектор - это вектор, проходящий сквозь курсор на экране.}

И кстати, не забывайте, что CylinderSensor можно привязывать к объектам любой формы, а не только к телам вращения. Просмотр. Потяните параллелепипед за боковые грани - вращение будет продолжаться, пока вы не дотянете ПРЯМОЛИНЕЙНО курсор до границы экрана. Теперь поверните ее к себе верхней гранью и крутите ее, пока не надоест, перемещая курсор ПО КРУГУ.

SphereSensor

Описание:

SphereSensor {
autoOffset TRUE
enabled TRUE
offset 0 0 0
eventOut isActive
eventOut rotation_changed
eventOut trackPoint_changed
}

После описания PlaneSensor и CylinderSensor здесь нечего рассказывать. Разве что пример привести. Просмотр.

Маршруты

Механизм route можно уподобить проводам, по которым передаются сигналы от eventOut к eventIn узлов или скриптов, можно от одного eventOut рассылать сообщения о событиях нескольким eventIn. И наоборот, к одному eventIn могут быть проложены маршруты от нескольких eventOut'ов. Но последней ситуации желательно избегать, поскольку, если на eventIn ОДНОВРЕМЕННО поступает два и более сообщений, результат не определен.

Просмотр. Щелкните на любом из объектов. Это пример, когда от одного eventOut сообщения рассылаются нескольким eventIn.

Интерполяторы

Как уже говорилось в начале страницы, интерполяторы выдают объекту численное значение какого-либо его параметра (цвет, положение, размер и т.д.) в данный момент времени в течение cycleInterval. За каждый cycleInterval интерполятор пробегает все значения полей key и keyValue.

Все узлы-интерполяторы (ColorInterpolator, CoordinateInterpolator, NormalInterpolator, OrientationInterpolator, PositionInterpolato, ScalarInterpolator) записываются одинаково:

eventIn set_fraction
key [набор контрольных точек]
keyValue [набор значений, соотвествующий точкам в поле key]

eventOut        value_changed

Отличия заключаются только в ТИПЕ значения, отсылаемого через eventOut value_changed.

Если число значений в поле keyValue не соответствует количеству контрольных точек в поле key, результат не определен.

Важно помнить, что значения keyValue в ПРОМЕЖУТОЧНЫХ точках между указанными контрольными точками интерполируется ЛИНЕЙНО ! Т.е. если Вы, скажем, хотите организовать поступательное движение объекта по дуге, нужно быть внимательным, вводя большое количество точек в поле key (и соответственно в поле keyValue), поскольку движение будет аппроксимироваться ломаной.

ColorInterpolator

Описание:

ColorInterpolator { eventIn set_fraction
key [ ]
keyValue [ ]
eventOut value_changed
}

Этот интерполятор позволяет отсылать объектам (через узел Material) RGB значения цвета.

Просмотр.

ColorInterpolator

Описание:

CoordinateInterpolator { eventIn set_fraction
key [ ]
keyValue [ ]
eventOut value_changed
}

Этот интерполятор позволяет отсылать наборы координат узлу Coordinate, который встречается в узлах IndexedFaceSet, IndexedLineSet и PointSet. Это удобно использовать, когда для деформации объекта не подходит анизотропное масштабирование.

Просмотр.

NormalInterpolator

Описание:

NormalInterpolator { eventIn set_fraction
key [ ]
keyValue [ ]
eventOut value_changed
}

Само понятие нормали (normal) возникает в VRML в уравнениях расчета освещенности каждой точки поверхности объекта (в частности в diffuse и specular color). А узел NormalInterpolator дает возможность динамически регулировать распределение освещенности по объекту за счет изменения направления векторов нормалей к граням объекта.

Просмотр.

OrientationInterpolator

Описание:

OrientationInterpolator { eventIn set_fraction
key [ ]
keyValue [ ]
eventOut value_changed
}

Нетрудно догадаться, что OrientationInterpolator позволяет организовать вращение. Только не забывайте, что значения ПРОМЕЖУТОЧНЫЕ между keyValues вычисляются из ЛИНЕЙНОЙ интерполяции.

Просмотр.

Назад | Содержание