Java3D
19 февраля 2004
Рубрика: Технологии.
Автор: Юрий Решетов.
pic

Java3D — это еще одна подтехнология Java. С ее помощью можно создавать трехмерные миры. Спрашивается, а для чего она нужна, ведь существует множество других технологий и пакетов 3D-графики.

1. Основное предназначение — расширение технологии самой Java, которая все время развивается и вбирает в себя только самое лучшее из других технологий. Причем это расширение еще и в области web-дизайна, так как приложения Java3D — апплеты, которые можно вставлять в web-страницы.

2. Пакеты или визуальные приложения 3D-графики скорее рассчитаны на создание одноразовых трехмерных изображений, которые впоследствии используются для оформления других приложений, web-страничек, мультиков или просто картинок. Java3D поддерживает почти все наиболее распространенные форматы графических файлов, создаваемых в подобных пакетах или приложениях, позволяя тем самым использовать их внутри своих программ. Следует учесть, что Java3D — это язык программирования высокого уровня, а потому гибкость, которую можно здесь достигнуть, намного превышает возможности визуальных средств разработки трехмерной графики. Существуют, конечно же, узкоспециализированные возможности программирования и в других пакетах, как AutoLisp в AutoCAD или скрипты в 3DstudioMax и прочие. Но много ли найдется желающих изучать эти направления программирования только ради создания графики, ведь они совершенно не пригодны для иных возможностей?

3. Многие визуальные графические пакеты и приложения не имеют встроенных средств разработки 3D-кинематики или, если имеют, то весьма ограниченные. Java3D позволяет создавать сложные изображения кинематических моделей, которыми можно как программно, так и интерактивно управлять. Например, мы можем задать траекторию движения графических объектов в 3Dmax и тем самым оживить сцену или часть сцены. Но это будет выглядеть как примитивная эмуляция, поскольку графические объекты, заданные таким жестким образом, уже не могут реагировать на различные события, например «столкновения» с другими объектами. А если быть еще точнее, то визуальное задание кинематики весьма трудоемко, так как приходится заведомо предусматривать все, что только возможно предусмотреть. А если чего не предусмотреть, то придется всю сцену рендерить заново. Сколько это будет стоить по времени и деньгам, лучше не напоминать — очень долго и слишком много. Поэтому даже мультипликаторы предпочитают «оживлять» свои персонажи с помощью живых актеров, кинематику которых считывают с датчиков и передают графическим станциям на обработку. Любой алгоритмический язык имеет здесь явное преимущество в гибкости только за счет того, что может обрабатывать условия. Поэтому в том же самом 3Dmax или иных графических пакетах применяют плагины. Но по той же причине, по какой не существует панацеи, кроме как в паранойном бреду, не существует и универсального плагина. А те, что имеют место, не могут пригодиться на все случаи жизни. Еще одно неудобство плагинов — отсутствие интерактивного управления и вмешательства в процессе создания изображений (за исключением написанных на AutoLisp).

pic

Из чего состоит Java3D? Это промежуточный API между Java-приложениями и графическими функциями операционной системы. В основном это функции видеокарт, поддерживающих OpenGL. Хотя для любителей OS MSWindows дополнительно распространяется API, управляющее графикой посредством DirectX. Поэтому для создания приложений c помощью Java3D необходимо иметь как минимум среду разработки Java-приложений с версией не менее Java2, любую видеокарту, поддерживающую стандарты OpenGL или DirectX, а также Java3D-API. Слухи о низкой скорости выполнения трехмерных приложений с помощью «медленной» Java следует отмести начисто, так как всю графическую обработку выполняет акселератор видеокарты, которому передаются только команды и аргументы. А значит, процессор компьютера используется по минимуму и в основном лишь для организации конвейера передачи этих команд. Поэтому, если графика выводится слишком медленно, то причина либо в аппаратном уровне, либо в неумении грамотно распределять графические ресурсы, что абсолютно независимо от среды разработки.

pic

Также следует упомянуть и дополнительные мультимедийные возможности данного пакета в области обработки, создания и воспроизведения звуковых анимаций.
Уже сейчас я спокойно пишу эти строки. Но когда был проинсталлирован пакет Java3D, включая документацию, сначала ничего не вызывало подозрений. Уже через полчасика я создал первый графический объект — танчик для игры «Robocode». Совсем иная ситуация возникла, когда захотелось этот самый танк «оживить» с помощью кинематики. И тут возник тупик. В документации (имеется в виду Tutorial) кинематике посвящено всего примерно 8 страниц первой главы. Но, как выяснилось, это совсем не то, что мне нужно. Здесь дано описание того, как заставить объект систематически вращаться вокруг своей оси (!?). А меня интересовал вопрос о том, как управлять движением или трансформацией объекта программно или интерактивно, например для создания игр. Всякая попытка реализовать подобные функции приводила лишь к сообщениям об ошибках, причем совершенно непонятным, что весьма странно для Java. Четвертая глава полностью посвящена интерактивному управлению, но ситуацию не прояснила, так как описывает привязку манипуляторов к приложению, с помощью которых можно, конечно же, двигать сцену или, выбирая курсором (pick) графические объекты, изменять их цвета и так далее.

pic

Но подобные трюки скорее нужны дизайнеру для оформления, нежели программисту. Пятая глава, полностью посвященная анимации, опять же ничего не дала, так как повторяла и дополняла опять же циклические движения, хотя и более сложные, например морфинг, но вовсе не управление ими. Демонстрационные примеры, которые прилагаются к пакету вместе с исходниками, также не имели того, что могло хотя бы подсказать. Естественно, чтобы не терять времени зря, пришлось лезть в Интернет и искать недостающее. Все, что мне удалось здесь найти, скорее, вызывало сожаление, нежели что-либо иное. Это было несколько жалких FAQ, посвященных тому, как установить Java3D на различных платформах. Форумы также весьма тщательно обходили тему Java3D, и если она не была офтопиком, то, будучи затронутой кем-либо, заканчивалась тщательным молчанием. Я буквально оторопел от всего этого, и возникло впечатление, что Java3D — либо недоработка, либо даже надувательство. Но причиной было ни то, ни другое, а только мое ламерство в 3D. Для того, чтобы создавать трехмерную графику, мало знать досконально программирование.

pic

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

pic

Принцип создания трехмерных Вселенных в Java3D аналогичен древовидной структуре, разработанной в Silicon Grpaphics и уже ставшей стандартом, то есть Вселенная начинается с корня дерева, расположенного в центре этой самой Вселенной, от которого исходят ветви в виде графических объектов. С геометрической точки зрения начало координат Вселенной расположено в корне. Каждая ветвь может иметь подветви, или 3D-изображения. Геометрически каждая ветвь имеет свой корень, а соответственно и свое начало координат, относительно которого располагаются объекты подветвей. К каждой ветви или подветви можно применять различные трансформации, то есть перемещать их относительно родительской ветви, поворачивать или изменять геометрию: сплющивать, изгибать, менять размеры и т.д. Все трансформации, применяемые к определенной ветви, автоматически распространяются на все ее подветви и 3D-объекты этих дочерних подветвей.
Например, если мы хотим изобразить объект ветвь — автомашину, то ее подветви: двигатель, двери, окна и пассажиры и так далее при перемещении или повороте самой машины относительно других объектов будут трансформироваться вместе с ней одновременно. Подветви можно снимать с одних ветвей и приживлять к другим. Например, мы можем высадить пассажира из автомашины, привязав его к ветви автодороги, и теперь перемещение автомобиля уже не будет влиять на его перемещение. Если нашу ветвь сплющить, например, изобразив дорожно-транспортное происшествие, то одновременно точно так же сплющатся и все объекты подветви. Если мы удалим ветвь, то подветви также исчезнут.

pic

Все безобразие представления графики в виде деревьев с ветвями заключается в том, что они очень удобны только для описания статических моделей. Но когда дело касается кинематики, то здесь запросто можно запутаться. Поэтому правильнее сразу перейти на иную модель представления в виде шарниров — каждую ветвь крепить через подвижный шарнир, и сразу все становится понятным. Например, наши руки — это не просто статические ветки, на которых закреплены плечи, предплечья, кисти, пальцы, а динамичный шарнирный механизм, где каждый сустав позволяет производить различные движения относительно других частей руки или тела. Помимо самих шарниров также необходимо иметь рычаги, посредством которых можно управлять тем, что расположено за шарнирами. И только в таком или аналогичном представлении можно уже приступать к кинематике.

pic

Основой или остовом в Java3D являются объекты класса BranchGroup (англ. — группа ветвей). Именно на ней и базируется все остальное, так как одна или даже единственная из таких групп — корень. Поэтому как минимум необходимо создать хотя бы один объект данного класса, чтобы размещать на нем все остальное. Далее идут примитивные графические объекты, ради которых все это создано — такие, как: цилиндр, сфера, параллепипед и конус, относящиеся к классу Primitive. Помимо примитивных объектов есть также множество иных, создаваемых с помощью комбинаций различных поверхностей, триангуляций или даже готовых и взятых из форматов различных графических пакетов. Примитивы можно сразу же размещать непосредственно на любой остов BranchGroup, но это вряд ли кому понадобится, так как объект располагается по умолчанию таким образом, чтобы его центр непосредственно совпадал с центром остова подветви. Поэтому также необходимо, помимо такого создания, еще и ориентировать объекты. Существует четыре вида ориентации (изменений положения и наклона): по осям координат относительно центра остова и три поворота вокруг этих осей относительно центра размещаемого объекта. Система координат соответственно — Декартова. А также множество различных трансформаций (изменений формы) объектов, выполняемых с помощью алгебры матриц (впрочем, ориентации — тоже алгебра матриц, но для каждой из них существуют соответствующие методы, а трансформации без матриц уже не осуществить, так как все их вариации невозможно перечислить, не говоря уже о создании отдельных методов для каждой). Чтобы ориентировать или трансформировать объекты, необходимо создать шарнир. А также еще нам понадобятся рычаги управления этим шарниром, посредством которых осуществляются ориентации и трансформации объектов, расположенных за шарниром. Шарниры в Java3D — это объекты класса TransformGroup, а рычаги Transform3D.
Порядок действий таков. Создаем остов, и рычаг(и), которые следует сразу же развернуть (задать матрицу или вызвать соответствующий метод, с помощью которой будет рассчитана ориентация создаваемой подветви), как нам необходимо. На базе рычаг(ов)а создаем шарнир, указав имя рычага в качестве аргумента конструктора. Хотя в шарнир их можно вставить и позднее, но никак не позже компиляции или отображения. После этого уже можно вставлять в ориентированный шарнир графические объекты или другие остова и шарниры. Слово «вставить» означает вызов метода addChild(), аргумент которого должен содержать имя вставляемого объекта. Осталось только шарнир с уже готовой подветвью также вставить в остов ветви, по отношению к которой трансформируемые им объекты будут являться отростками. Эти самые объекты, вставленные таким образом в подобный шарнир, будут ориентированы по осям координат относительно остова, что было задано рычаг(ами)ом. Следует учесть и то, что ни остовы, ни шарниры, ни рычаги графически не отображаются, хотя являются вычислительными ресурсами компьютера. Видны будут только графические объекты.

pic

Нельзя дважды связывать объекты через параллельные шарнирные соединения. С точки зрения физики это неверно, так как железнодорожный локомотив опирается на основу (тележки с колесными парами) через два шарнирных соединения, автомобиль четыре по числу колес (если не считать домкрат), а гусеничный транспорт и того более. Все дело в том, что физически, если мы произведем изменения в одном из шарниров, они через корпус передадутся на другие. В 3D-графике положение совсем иное, так как здесь структура древовидная и получается все наоборот, то есть не корпус оказывает влияние на шарниры, соединяющие его с опорой, а от опоры через шарниры можно влиять на движение корпуса. Это аналогично тому, что вращать землю под неподвижным автомобилем, чтобы он мог перемещаться относительно ее поверхности, а не колеса этого самого авто. Поэтому при создании модели транспортного средства, чтобы обеспечить кинематику его относительно модели поверхности дороги, необходимо создать шарнир между корпусом и дорожным покрытием, а не через колесо, а тем более колеса. Сами же колеса при этом не будут иметь никакого сцепления с дорогой, а лишь болтаться в качестве прибамбасов. А это значит, что для эмуляции их вращения придется писать подпрограмму, которая через скорость перемещения вычислит круговое вращение колес. И естественно, что шарниры запараллеливать здесь нельзя, поскольку их рычаги значениями своих матриц задают направление или трансформации подветви. Если мы от опоры до объекта проведем более одного шарнирного соединения, то OpenGL не сможет разобраться, из какого из них необходимо брать значения для ориентаций и трансформаций. И в результате выдаст сообщение об ошибке с отказом продолжать действия. С точки зрения Java многочисленные параллельные ссылки на одни и те же объекты ошибкой не являются и даже используются во многих алгоритмах, например двусвязных списках. Компилятор Java спокойно сформирует код. Сообщение появится лишь после попытки запуска приложения. Поэтому Java здесь бессильна помочь чем-либо. И получив сообщение об ошибке от OpenGL, просто выкинет исключительную ситуацию и аварийно завершит приложение. Причем в данном случае виртуальная машина не в состоянии указать местонахождение ошибки, так как у нее нет телепатических способностей относительно того, какое из сформированных шарнирных соединений является лишним. Восстановить работоспособность приложения с помощью обработки исключения тоже не удастся, поскольку графическая часть уже отказалась от дальнейших действий, а сама Java является лишь ее посредником. Спасает ситуацию то, что если приложение вывалилось в самом начале и строка с местонахождением ошибки в сообщении не указана, то значит, следует обязательно проверить иерархию на предмет параллельности.

pic

Еще одна особенность Java3D (и не только) заключается в том, что невозможно одновременно применять один и тот же рычаг для различных ориентаций и трансформаций. Один рычаг может либо указать только координаты объекта относительно центра остова, либо одно из вращений вокруг какой-либо оси координат, либо трансформацию. Правильнее сказать, что нет стандартных методов, которые бы позволяли сделать это в один прием, за исключением создания матрицы с готовыми значениями, то есть статическими. И если вы с алгеброй матриц и афинными преобразованиями на ты, то карты в руки, хотя это бесполезно, о чем будет сказано чуть позже, когда речь пойдет о компиляции. Но если нам необходимо применить несколько таких методов одновременно, то придется для каждого из них создавать свой рычаг и связывать их друг с другом c помощью метода mul(), то есть умножения матриц. Конечный — результирующий рычаг, сформированный таким макаром, можно вставить в шарнир или указать его имя в качестве аргумента конструктора шарнира.
Внешне такое применение рычагов кажется не слишком понятным, поскольку их необходимо сначала ориентировать, а лишь потом вставлять, не говоря уже о том, что заданные таким образом действия будут выполнены только впоследствии при компиляции или прорисовке. Скорее, подобная метода более похожа на закладывание программы. Для статических изображений это действительно так, но в кинематике только ориентацией рычага можно управлять объектами, расположенными на шарнире, в который этот самый рычаг вставлен. Например, при интерактивном управлении мы можем к рычагу привязать интерфейс джойстика, клавиатуры, мышки, стилуса или любого иного манипулятора. Соответственно, если нам будет необходимо выполнить несколько функционально различных ориентаций и трансформаций, то опять же придется создавать для них несколько управляющих рычагов.
Также шарниры могут быть жестко зафиксированы по умолчанию, а могут управляться с помощью рычагов после компиляции сцены, если применить к ним метод, изменяющий соответствующие флаги — setCapability (TransformGroup. ALLOW_TRANSFORM_WRITE). На первый взгляд подобное разделение может показаться абсолютно абсурдным, ведь независимо от того, жестко закреплен шарнир или нет, но объекты за ним даже не пошелохнутся, если не трогать рычаги — это ведь не физический мир. Но, с точки зрения графической системы, ситуация совсем иная. Фиксированные шарниры компилируются, а гибкие интерпретируются. Дело в том, что при компиляции из последовательности соседних фиксированных шарниров создается один-единственный. Ведь трансформации и ориентации — это алгебраические операции над матрицами. А следовательно, результатом этих операций становится одна-единственная матрица, которая и формируется в процессе компиляции из соседних жестких шарниров. Для гибких же ситуация совершенно иная, так как значения в матрице могут в любой момент измениться, а значит, графической системе приходится перед прорисовкой объектов последовательно проводить все алгебраические операции над всеми матрицами шарниров, начиная от остова и до ветви, ими управляемой. А следовательно, не стоит злоупотреблять излишней гибкостью, а также не перемежать жесткие шарниры с гибкими без всякой на то необходимости.

pic

Для осуществления кинематики понадобятся как минимум два шарнира: фиксированный и подвижны(е)й, вставленные последовательно от остова и до графических объектов, ими трансформируемых. Фиксированный позволяет разместить и сориентировать базу объектов первоначально и относительно центра остова. А подвижны(е)й уже позволя(ю)ет управлять их ориентацией относительно родительской ветви впоследствии посредством рычагов. Почему так много? Да потому, что с помощью одного рычага можно осуществлять только одну-единственную трансформацию или ориентацию. Метод mul() в кинематике уже не поможет. Соответственно, сколько необходимо рычагов, столько и шарниров, плюс один фиксированный.
Ну и наконец, кинематика 3D — это не физическая кинематика, а относительная. Графические объекты не перемещаются и расстояния не приращиваются к уже достигнутым, а непосредственно располагаются при внесении изменений относительно начала координат подветви базиса или разворачиваются на заданные углы вокруг нее. Следовательно, каждый раз нужно задавать новые угловые и линейные координаты от того места, с которого объект был скомпилирован. В физике таких явлений не существует, а они относятся скорее к парапсихологическим телепортациям. Но если в 2D-графике, чтобы не терять времени на создание и удаление объектов, можно уже готовые прикрыть другими объектами или телепортировать за пределы видимости и возвращать при необходимости, то здесь подобные фокусы приведут к обратному эффекту, так как скрытый объект все равно будет вычислен на предмет попадания в область видимости, а также отражаемых бликов и теней. Поэтому следует удалять скрытые объекты из графической системы, обнуляя (имеется в виду null, а не ноль) соответствующую подветвь. А чтобы не создавать их заново, сохранять ссылку в полях Java. В любом случае формирование или удаление объектов в Java менее ресурсоемко, нежели хранение неиспользуемых в графической системе.

Вот собственно и все, что касается теории. Но это как раз то, без чего бесполезно приступать к практике

Orphus system
В Telegram
В Одноклассники
ВКонтакте