Как GWT открывает дополненную реальность в вашем браузере
Опубликовано: 2022-03-11В нашем предыдущем посте о GWT Web Toolkit мы обсудили сильные стороны и характеристики GWT, который, если вспомнить общую идею, позволяет нам транспилировать исходный код Java в JavaScript и беспрепятственно смешивать библиотеки Java и JavaScript. Мы отметили, что JavaScript, сгенерированный GWT, значительно оптимизирован.
В сегодняшнем посте мы хотели бы немного углубиться и увидеть инструментарий GWT в действии. Мы продемонстрируем, как мы можем использовать преимущества GWT для создания особого приложения: веб-приложения с дополненной реальностью (AR), которое работает в режиме реального времени, полностью на JavaScript, в браузере.
В этой статье мы сосредоточимся на том, как GWT дает нам возможность легко взаимодействовать со многими API-интерфейсами JavaScript, такими как WebRTC и WebGL, и позволяет нам использовать большую библиотеку Java, NyARToolkit, которая никогда не предназначалась для использования в браузере. Мы покажем, как GWT позволил моей команде и мне в Jooink собрать все эти части вместе, чтобы создать наш любимый проект, Picshare , приложение AR на основе маркеров, которое вы можете попробовать в своем браузере прямо сейчас.
Этот пост не будет исчерпывающим пошаговым руководством по созданию приложения, а скорее продемонстрирует использование GWT для легкого преодоления, казалось бы, непреодолимых проблем.
Обзор проекта: от реальности к дополненной реальности
Picshare использует дополненную реальность на основе маркеров. Этот тип приложения AR ищет в сцене маркер : определенный, легко распознаваемый геометрический узор, подобный этому. Маркер предоставляет информацию о положении и ориентации отмеченного объекта, позволяя программному обеспечению реалистично проецировать дополнительные трехмерные пейзажи на изображение. Основные шаги в этом процессе:
- Доступ к камере: при работе с собственными настольными приложениями операционная система обеспечивает доступ ввода-вывода к большей части аппаратного обеспечения устройства. Это не то же самое, когда мы имеем дело с веб-приложениями. Браузеры были созданы как своего рода «песочница» для кода JavaScript, загружаемого из сети, и изначально не предназначались для того, чтобы веб-сайты могли взаимодействовать с большинством аппаратных средств устройств. WebRTC преодолевает этот барьер, используя функции захвата мультимедиа HTML5, позволяя браузеру, среди прочего, получать доступ к камере устройства и ее потоку.
- Анализ видеопотока: у нас есть видеопоток… что теперь? Мы должны анализировать каждый кадр, чтобы обнаружить маркеры и вычислить положение маркера в реконструированном трехмерном мире. Эта сложная задача — дело NyARToolkit.
- Дополнить видео. Наконец, мы хотим отобразить исходное видео с добавлением синтетических 3D-объектов. Мы используем WebGL, чтобы нарисовать окончательную расширенную сцену на веб-странице.
Использование преимуществ API HTML5 с помощью GWT
Использование API-интерфейсов JavaScript, таких как WebGL и WebRTC, обеспечивает неожиданное и необычное взаимодействие между браузером и пользователем.
Например, WebGL допускает аппаратное ускорение графики и с помощью спецификации типизированного массива позволяет движку JavaScript выполнять обработку чисел почти с естественной производительностью. Точно так же с WebRTC браузер может получать доступ к потокам видео (и других данных) непосредственно с аппаратного обеспечения компьютера.
WebGL и WebRTC — это библиотеки JavaScript, которые должны быть встроены в веб-браузер. Большинство современных браузеров HTML5 имеют по крайней мере частичную поддержку обоих API (как вы можете видеть здесь и здесь). Но как мы можем использовать эти инструменты в GWT, написанном на Java? Как обсуждалось в предыдущем посте, уровень функциональной совместимости GWT, JsInterop (официально выпущенный в GWT 2.8), упрощает эту задачу.
Использовать JsInterop с GWT 2.8 так же просто, как добавить -generateJsInteropExports
в качестве аргумента компилятору. Доступные аннотации определены в пакете jsinterop.annotations
, входящем в состав gwt-user.jar
.
WebRTC
Например, с минимальной работой по кодированию использование getUserMedia
WebRTC в Chrome с GWT становится таким же простым, как написание:
Navigator.webkitGetUserMedia( configs, stream -> video.setSrc( URL.createObjectURL(stream) ), e -> Window.alert("Error: " + e) );
Где класс Navigator
можно определить следующим образом:
@JsType(namespace = JsPackage.GLOBAL, isNative = true, name="navigator") final static class Navigator { public static native void webkitGetUserMedia( Configs configs, SuccessCallback success, ErrorCallback error); }
Представляет интерес определение интерфейсов SuccessCallback
и ErrorCallback
, реализованных лямбда-выражением выше и определенных в Java с помощью аннотации @JsFunction
:
@JsFunction public interface SuccessCallback { public void onMediaSuccess(MediaStream stream); } @JsFunction public interface ErrorCallback { public void onError(DomException error); }
Наконец, определение URL
-адреса класса почти идентично определению Navigator
, и аналогично класс Configs
может быть определен следующим образом:
@JsType(namespace = JsPackage.GLOBAL, isNative = true, name="Object") public static class Configs { @JsProperty public native void setVideo(boolean getVideo); }
Фактическая реализация всех этих функций происходит в движке JavaScript браузера.
Вы можете найти приведенный выше код на GitHub здесь.
В этом примере для простоты используется устаревший API navigator.getUserMedia()
, поскольку он единственный работает без полифилла в текущей стабильной версии Chrome. В рабочем приложении мы можем использовать adapter.js для доступа к потоку через более новый API navigator.mediaDevices.getUserMedia()
, единообразно во всех браузерах, но это выходит за рамки настоящего обсуждения.
WebGL
Использование WebGL из GWT не сильно отличается от использования WebRTC, но немного более утомительно из-за присущей стандарту OpenGL сложности.
Наш подход здесь отражает подход, использованный в предыдущем разделе. Результат переноса можно увидеть в реализации GWT WebGL, используемой в Picshare , которую можно найти здесь, а пример результатов, полученных с помощью GWT, можно найти здесь.
Включение WebGL само по себе не дает нам возможности трехмерной графики. Как пишет Грегг Таварес:
Чего многие люди не знают, так это того, что WebGL на самом деле является 2D API, а не 3D API.
3D-арифметика должна выполняться другим кодом и преобразовываться в 2D-изображение для WebGL. Есть несколько хороших библиотек GWT для 3D-графики WebGL. Мой любимый — Parallax, но для первой версии Picshare мы пошли по пути «сделай сам», написав небольшую библиотеку для рендеринга простых 3D-сеток. Библиотека позволяет нам определять перспективную камеру и управлять сценой объектов. Не стесняйтесь проверить это здесь.
Компиляция сторонних библиотек Java с помощью GWT
NyARToolkit — это чисто Java-порт ARToolKit, программной библиотеки для создания приложений дополненной реальности. Порт был написан японскими разработчиками из Nyatla. Хотя исходный ARToolKit и версия Nyatla несколько разошлись с момента первоначального порта, NyARToolkit по-прежнему активно поддерживается и улучшается.
AR на основе маркеров является специализированной областью и требует навыков компьютерного зрения, обработки цифровых изображений и математики, как видно здесь:
Воспроизведено из документации ARToolKit.
Воспроизведено из документации ARToolKit.
Все алгоритмы, используемые набором инструментов, задокументированы и хорошо изучены, но их переписывание с нуля — длительный и подверженный ошибкам процесс, поэтому предпочтительнее использовать существующий проверенный набор инструментов, такой как ARToolKit. К сожалению, при таргетинге в Интернете такая вещь недоступна. Большинство мощных и продвинутых наборов инструментов не имеют реализации на языке JavaScript, который в основном используется для управления документами и данными в формате HTML. Именно здесь GWT доказывает свою неоценимую силу, позволяя нам просто преобразовать NyARToolkit в JavaScript и использовать его в веб-приложении с минимальными трудностями.
Компиляция с помощью GWT
Поскольку проект GWT по сути является проектом Java, использование NyARToolkit — это просто вопрос импорта исходных файлов по исходному пути. Однако обратите внимание, что поскольку транспиляция кода GWT в JavaScript выполняется на уровне исходного кода, вам нужны исходники NyARToolkit, а не только JAR с скомпилированными классами.
Библиотеку, используемую Picshare , можно найти здесь. Это зависит только от пакетов, найденных внутри lib/src
и lib/src.markersystem
из сборки NyARToolkit, заархивированной здесь. Мы должны скопировать и импортировать эти пакеты в наш проект GWT.

Мы должны хранить эти сторонние пакеты отдельно от нашей собственной реализации, но для продолжения «GWT-изации» NyARToolkit мы должны предоставить файл конфигурации XML, который информирует компилятор GWT, где искать источники. В пакет jp.nyatla.nyartoolkit
добавляем файл NyARToolkit.gwt.xml
<module> <source path="core" /> <source path="detector" /> <source path="nyidmarker" /> <source path="processor" /> <source path="psarplaycard" /> <source path="markersystem" /> </module>
Теперь в нашем основном пакете com.jooink.gwt.nyartoolkit
мы создаем основной файл конфигурации GWT_NyARToolKit.gwt.xml
и указываем компилятору включить исходный код Nyatla в classpath путем наследования из его XML-файла:
<inherits name='jp.nyatla.nyartoolkit.NyARToolkit'/>
На самом деле довольно легко. В большинстве случаев этого будет достаточно, но, к сожалению, мы еще не закончили. Если мы попытаемся скомпилировать или выполнить в режиме Super Dev Mode на этом этапе, мы столкнемся с довольно неожиданной ошибкой:
No source code is available for type java.io.InputStream; did you forget to inherit a required module?
Причина этого в том, что NyARToolkit (то есть библиотека Java, предназначенная для проектов Java) использует классы JRE, которые не поддерживаются эмулируемой JRE GWT. Мы кратко обсуждали это в предыдущем посте.
В данном случае проблема связана с InputStream
и связанными с ним классами ввода-вывода. Как оказалось, нам даже не нужно использовать большинство этих классов, но нам нужно предоставить компилятору некоторую реализацию. Что ж, мы могли бы потратить уйму времени на ручное удаление этих ссылок из исходного кода NyARToolkit, но это было бы сумасшествием. GWT предлагает лучшее решение: предоставить собственные реализации неподдерживаемых классов с помощью XML-тега <super-source>
.
<super-source>
Как описано в официальной документации:
Тег
<super-source>
указывает компилятору переустановить исходный путь. Это полезно в случаях, когда вы хотите повторно использовать существующий Java API для проекта GWT, но исходный код недоступен или непереводим. Распространенной причиной этого является эмуляция части JRE, не реализованной GWT.
Итак <super-source>
— это именно то, что нам нужно.
Мы можем создать каталог jre
в проекте GWT, куда мы можем поместить наши реализации для классов, которые вызывают у нас проблемы:
java.io.FileInputStream java.io.InputStream java.io.InputStreamReader java.io.StreamTokenizer java.lang.reflect.Array java.nio.ByteBuffer java.nio.ByteOrder
Все они, кроме java.lang.reflect.Array
, на самом деле не используются, поэтому нам нужны только глупые реализации. Например, наш FileInputStream
выглядит следующим образом:
package java.io; import java.io.InputStream; import com.google.gwt.user.client.Window; public class FileInputStream extends InputStream { public FileInputStream(String filename) { Window.alert("WARNING, FileInputStream created with filename: " + filename ); } @Override public int read() { return 0; } }
Оператор Window.alert
в конструкторе полезен во время разработки. Хотя мы должны иметь возможность скомпилировать класс, мы хотим убедиться, что никогда не используем его на самом деле, поэтому это предупредит нас в случае непреднамеренного использования класса.
java.lang.reflect.Array
на самом деле используется нужным нам кодом, поэтому требуется не совсем тупая реализация. Это наш код:
package java.lang.reflect; import jp.nyatla.nyartoolkit.core.labeling.rlelabeling.NyARRleLabelFragmentInfo; import jp.nyatla.nyartoolkit.markersystem.utils.SquareStack; import com.google.gwt.user.client.Window; public class Array { public static <T> Object newInstance(Class<T> c, int n) { if( NyARRleLabelFragmentInfo.class.equals(c)) return new NyARRleLabelFragmentInfo[n]; else if(SquareStack.Item.class.equals(c)) return new SquareStack.Item[n]; else Window.alert("Creating array of size " + n + " of " + c.toString()); return null; } }
Теперь, если мы поместим <super-source path="jre"/>
в файл модуля GWT_NyARToolkit.gwt.xml
, мы сможем безопасно скомпилировать и использовать NyARToolkit в нашем проекте!
Склеивание всего вместе с GWT
Теперь мы находимся в состоянии иметь:
- WebRTC — технология, способная принимать поток с веб-камеры и отображать его в
<video>
. - WebGL, технология, позволяющая управлять графикой с аппаратным ускорением в HTML
<canvas>
. - NyARToolkit, Java-библиотека, способная брать изображение (в виде массива пикселей), искать маркер и, если он найден, давать нам матрицу преобразования, которая полностью определяет положение маркера в трехмерном пространстве.
Сейчас задача состоит в том, чтобы интегрировать все эти технологии вместе.
Мы не будем вдаваться в подробности того, как это сделать, но основная идея состоит в том, чтобы использовать видеоизображение в качестве фона нашей сцены (текстура, примененная к «дальней» плоскости на изображении выше) и построить трехмерную структуру данных. что позволяет нам спроецировать это изображение в космос, используя результаты NyARToolkit.
Эта конструкция дает нам правильную структуру для взаимодействия с библиотекой NyARToolkit для распознавания маркеров и рисования 3D-модели поверх сцены с камеры.
Сделать поток камеры пригодным для использования немного сложно. Видеоданные могут быть отображены только в элементе <video>
. Элемент HTML5 <video>
непрозрачен и не позволяет нам извлекать данные изображения напрямую, поэтому мы вынуждены копировать видео в промежуточный элемент <canvas>
, извлекать данные изображения, преобразовывать их в массив пикселей и, наконец, отправьте его в метод Sensor.update()
. Затем NyARToolkit может выполнить работу по идентификации маркера на изображении и возврату матрицы преобразования, соответствующей его положению в нашем трехмерном пространстве.
С помощью этих элементов мы можем расположить синтетический объект точно над маркером, в 3D, в прямом видеопотоке! Благодаря высокой производительности GWT у нас есть много вычислительных ресурсов, поэтому мы можем даже применить к холсту некоторые видеоэффекты, такие как сепия или размытие, прежде чем использовать его в качестве фона для сцены WebGL.
Следующий сокращенный код описывает суть процесса:
// given a <canvas> drawing context with appropriate width and height // and a <video> where the mediastream is drawn ... // for each video frame // draw the video frame on the canvas ctx.drawImage(video, 0, 0, w, h); // extract image data from the canvas ImageData capt = ctx.getImageData(0, 0, w, h); // convert the image data in a format acceptable by NyARToolkit ImageDataRaster input = new ImageDataRaster(capt); // push the image in to a NyARSensor sensor.update(input); // update the NyARMarkerSystem with the sensor nyar.update(sensor); // the NyARMarkerSystem contains information about the marker patterns and is able to detect them. // After the call to update, all the markers are detected and we can get information for each // marker that was found. if( nyar.isExistMarker( marker_id ) ) { NyARDoubleMatrix44 m = nyar.getMarkerMatrix(marker_id); // m is now the matrix representing the pose (position and orientation) of // the marker in the scene, so we can use it to superimpose an object of // our choice ... } ...
С помощью этой техники мы можем получить такие результаты:
Это процесс, который мы использовали для создания Picshare , где вам предлагается распечатать маркер или отобразить его на своем мобильном телефоне и поиграть с дополненной реальностью на основе маркеров в своем браузере. Наслаждаться!
Заключительные замечания
Picshare — это долгосрочный любимый проект для нас в Jooink. Первая реализация появилась несколько лет назад, и даже тогда она была достаточно быстрой, чтобы впечатлять. По этой ссылке вы можете увидеть один из наших ранних экспериментов, собранный в 2012 году и ни разу не затронутый. Обратите внимание, что в примере есть только один <video>
. Два других окна представляют собой элементы <canvas>
, отображающие результаты обработки.
GWT был достаточно мощным даже в 2012 году. С выпуском GWT 2.8 мы получили значительно улучшенный уровень взаимодействия с JsInterop, что еще больше повысило производительность. Кроме того, к празднованию многих, мы также получили гораздо лучшую среду разработки и отладки, Super Dev Mode. Ах да, и поддержка Java 8.
Мы с нетерпением ждем GWT 3.0!