Jak GWT odblokowuje rozszerzoną rzeczywistość w Twojej przeglądarce?

Opublikowany: 2022-03-11

W naszym poprzednim poście na GWT Web Toolkit omówiliśmy mocne strony i cechy GWT, które, aby przypomnieć ogólną ideę, pozwala nam transpilować kod źródłowy Java do JavaScript i bezproblemowo mieszać biblioteki Java i JavaScript. Zauważyliśmy, że JavaScript generowany przez GWT jest radykalnie zoptymalizowany.

W dzisiejszym poście chcielibyśmy wejść nieco głębiej i zobaczyć zestaw narzędzi GWT w akcji. Pokażemy, jak możemy wykorzystać GWT do zbudowania specyficznej aplikacji: aplikacji internetowej rozszerzonej rzeczywistości (AR), która działa w czasie rzeczywistym, w pełni w JavaScript, w przeglądarce.

Rozszerzona rzeczywistość w przeglądarce? To łatwiejsze niż myślisz.

W tym artykule skupimy się na tym, w jaki sposób GWT daje nam możliwość łatwej interakcji z wieloma interfejsami API JavaScript, takimi jak WebRTC i WebGL, i pozwala nam wykorzystać dużą bibliotekę Java, NyARToolkit, która nigdy nie była przeznaczona do użycia w przeglądarce. Pokażemy, w jaki sposób GWT pozwoliło mojemu zespołowi i mnie w Jooink połączyć wszystkie te elementy, aby stworzyć nasz ulubiony projekt, Picshare , aplikację AR opartą na znacznikach, którą możesz teraz wypróbować w przeglądarce.

Ten post nie będzie wyczerpującym przewodnikiem po tym, jak zbudować aplikację, ale raczej pokaże użycie GWT do łatwego pokonywania pozornie przytłaczających wyzwań.

Przegląd projektu: od rzeczywistości do rzeczywistości rozszerzonej

Potok dla rzeczywistości rozszerzonej opartej na znacznikach w przeglądarce z GWT, z WebRTC, WebGL i ARToolKit.

Picshare wykorzystuje rozszerzoną rzeczywistość opartą na znacznikach. Ten typ aplikacji AR przeszukuje scenę w poszukiwaniu znacznika : określonego, łatwo rozpoznawalnego wzoru geometrycznego, takiego jak ten. Znacznik dostarcza informacji o położeniu i orientacji zaznaczonego obiektu, dzięki czemu oprogramowanie może w realistyczny sposób rzutować dodatkową scenerię 3D na obraz. Podstawowe kroki w tym procesie to:

  • Uzyskaj dostęp do kamery: W przypadku natywnych aplikacji komputerowych system operacyjny zapewnia dostęp we/wy do większości sprzętu urządzenia. To nie to samo, gdy mamy do czynienia z aplikacjami webowymi. Przeglądarki zostały zbudowane tak, aby były czymś w rodzaju „piaskownicy” dla kodu JavaScript pobranego z sieci i pierwotnie nie miały umożliwiać witrynom interakcji z większością urządzeń sprzętowych. WebRTC przełamuje tę barierę, korzystając z funkcji przechwytywania multimediów HTML5s, umożliwiając przeglądarce dostęp między innymi do kamery urządzenia i jej strumienia.
  • Przeanalizuj strumień wideo: Mamy strumień wideo… co teraz? Musimy przeanalizować każdą klatkę, aby wykryć znaczniki i obliczyć położenie znacznika w zrekonstruowanym świecie 3D. To złożone zadanie to biznes NyARToolkit.
  • Rozszerz wideo: Na koniec chcemy wyświetlić oryginalne wideo z dodanymi syntetycznymi obiektami 3D. Używamy WebGL, aby narysować ostateczną, rozszerzoną scenę na stronie internetowej.

Korzystanie z interfejsów API HTML5 za pomocą GWT

Korzystanie z interfejsów API JavaScript, takich jak WebGL i WebRTC, umożliwia nieoczekiwane i nietypowe interakcje między przeglądarką a użytkownikiem.

Na przykład WebGL pozwala na sprzętową akcelerację grafiki i, za pomocą specyfikacji tablic typowanych, umożliwia silnikowi JavaScript wykonywanie przetwarzania liczb z niemal natywną wydajnością. Podobnie w przypadku WebRTC przeglądarka może uzyskać dostęp do strumieni wideo (i innych danych) bezpośrednio ze sprzętu komputerowego.

WebGL i WebRTC to biblioteki JavaScript, które muszą być wbudowane w przeglądarkę internetową. Większość nowoczesnych przeglądarek HTML5 ma przynajmniej częściową obsługę obu interfejsów API (jak widać tutaj i tutaj). Ale jak możemy wykorzystać te narzędzia w GWT napisanym w Javie? Jak wspomniano w poprzednim poście, warstwa interoperacyjności GWT, JsInterop (wydana oficjalnie w GWT 2.8), sprawia, że ​​jest to bułka z masłem.

Używanie JsInterop z GWT 2.8 jest tak proste, jak dodanie -generateJsInteropExports jako argumentu do kompilatora. Dostępne adnotacje są zdefiniowane w pakiecie jsinterop.annotations , dołączonym do gwt-user.jar .

WebRTC

Na przykład, przy minimalnym nakładzie pracy nad kodowaniem, użycie getUserMedia WebRTC w Chrome z GWT staje się tak proste, jak pisanie:

 Navigator.webkitGetUserMedia( configs, stream -> video.setSrc( URL.createObjectURL(stream) ), e -> Window.alert("Error: " + e) );

Gdzie klasę Navigator można zdefiniować w następujący sposób:

 @JsType(namespace = JsPackage.GLOBAL, isNative = true, name="navigator") final static class Navigator { public static native void webkitGetUserMedia( Configs configs, SuccessCallback success, ErrorCallback error); }

Interesująca jest definicja interfejsów SuccessCallback i ErrorCallback , zarówno zaimplementowanych w powyższym wyrażeniu lambda, jak i zdefiniowanych w Javie za pomocą adnotacji @JsFunction :

 @JsFunction public interface SuccessCallback { public void onMediaSuccess(MediaStream stream); } @JsFunction public interface ErrorCallback { public void onError(DomException error); }

Wreszcie definicja URL klasy jest prawie identyczna jak w Navigator , podobnie klasa Configs może być zdefiniowana przez:

 @JsType(namespace = JsPackage.GLOBAL, isNative = true, name="Object") public static class Configs { @JsProperty public native void setVideo(boolean getVideo); }

Faktyczna implementacja wszystkich tych funkcjonalności odbywa się w silniku JavaScript przeglądarki.

Powyższy kod można znaleźć na GitHub tutaj.

W tym przykładzie, dla uproszczenia, używany jest przestarzały interfejs API navigator.getUserMedia() , ponieważ jako jedyny działa bez wypełniania wielokrotnego w bieżącej stabilnej wersji przeglądarki Chrome. W aplikacji produkcyjnej możemy użyć adapter.js, aby uzyskać dostęp do strumienia za pośrednictwem nowszego interfejsu API navigator.mediaDevices.getUserMedia() , jednakowo we wszystkich przeglądarkach, ale wykracza to poza zakres niniejszej dyskusji.

WebGL

Korzystanie z WebGL z GWT nie różni się zbytnio od korzystania z WebRTC, ale jest nieco bardziej nużące ze względu na wewnętrzną złożoność standardu OpenGL.

Nasze podejście tutaj odzwierciedla podejście zastosowane w poprzedniej sekcji. Wynik zawijania można zobaczyć w implementacji GWT WebGL używanej w Picshare , którą można znaleźć tutaj, a przykład wyników wygenerowanych przez GWT można znaleźć tutaj.

Samo włączenie WebGL nie daje nam możliwości grafiki 3D. Jak pisze Gregg Tavares:

Wiele osób nie wie, że WebGL jest w rzeczywistości interfejsem API 2D, a nie 3D.

Arytmetyka 3D musi być wykonana przez inny kod i przekształcona w obraz 2D dla WebGL. Istnieje kilka dobrych bibliotek GWT dla grafiki 3D WebGL. Moją ulubioną jest Parallax, ale w przypadku pierwszej wersji Picshare podążaliśmy ścieżką bardziej „zrób to sam”, tworząc małą bibliotekę do renderowania prostych siatek 3D. Biblioteka pozwala nam zdefiniować kamerę perspektywiczną oraz zarządzać sceną obiektów. Zapraszam do sprawdzenia tutaj.

Kompilowanie bibliotek Java innych firm za pomocą GWT

NyARToolkit to czysty Java port ARToolKit, biblioteki oprogramowania do tworzenia aplikacji rozszerzonej rzeczywistości. Port został napisany przez japońskich programistów z firmy Nyatla. Chociaż oryginalny ARToolKit i wersja Nyatla różnią się nieco od oryginalnego portu, NyARToolkit jest nadal aktywnie utrzymywany i ulepszany.

AR oparta na znacznikach jest wyspecjalizowaną dziedziną i wymaga umiejętności w zakresie widzenia komputerowego, cyfrowego przetwarzania obrazu i matematyki, co widać tutaj:

Analiza obrazu rzeczywistości rozszerzonej oparta na markerach za pomocą ARToolKit.

Powielono z dokumentacji ARToolKit.

Potok rzeczywistości rozszerzonej oparty na markerach z ARToolKit.

Powielono z dokumentacji ARToolKit.

Wszystkie algorytmy używane przez zestaw narzędzi są udokumentowane i dobrze zrozumiane, ale przepisywanie ich od zera jest procesem długotrwałym i podatnym na błędy, dlatego lepiej jest użyć istniejącego, sprawdzonego zestawu narzędzi, takiego jak ARToolKit. Niestety w przypadku kierowania na sieć nie ma czegoś takiego. Najbardziej zaawansowane, zaawansowane zestawy narzędzi nie mają implementacji w JavaScript, języku używanym głównie do manipulowania dokumentami i danymi HTML. W tym miejscu GWT udowadnia swoją nieocenioną siłę, umożliwiając nam po prostu transpilację NyARToolkit do JavaScript i użycie go w aplikacji internetowej przy bardzo niewielkim wysiłku.

Kompilowanie z GWT

Ponieważ projekt GWT jest zasadniczo projektem Java, użycie NyARToolkit to tylko kwestia importowania plików źródłowych w ścieżce źródłowej. Należy jednak pamiętać, że ponieważ transpilacja kodu GWT do JavaScript odbywa się na poziomie kodu źródłowego, potrzebne są źródła NyARToolkit, a nie tylko JAR ze skompilowanymi klasami.

Bibliotekę używaną przez Picshare można znaleźć tutaj. Zależy tylko od pakietów znalezionych w lib/src i lib/src.markersystem z kompilacji NyARToolkit zarchiwizowanej tutaj. Musimy skopiować i zaimportować te pakiety do naszego projektu GWT.

Powinniśmy trzymać te pakiety innych firm oddzielnie od naszej własnej implementacji, ale aby kontynuować „wizację GWT” NyARToolkit, musimy dostarczyć plik konfiguracyjny XML, który informuje kompilator GWT, gdzie szukać źródeł. W pakiecie jp.nyatla.nyartoolkit dodajemy plik NyARToolkit.gwt.xml

 <module> <source path="core" /> <source path="detector" /> <source path="nyidmarker" /> <source path="processor" /> <source path="psarplaycard" /> <source path="markersystem" /> </module>

Teraz w naszym głównym pakiecie, com.jooink.gwt.nyartoolkit , tworzymy główny plik konfiguracyjny, GWT_NyARToolKit.gwt.xml , i instruujemy kompilator, aby dołączył źródło Nyatla do ścieżki klasy, dziedzicząc z jego pliku XML:

 <inherits name='jp.nyatla.nyartoolkit.NyARToolkit'/>

Właściwie to całkiem proste. W większości przypadków to wystarczy, ale niestety jeszcze nie skończyliśmy. Jeśli na tym etapie spróbujemy skompilować lub wykonać w trybie Super Dev Mode, natkniemy się na błąd, który jest dość zaskakujący:

 No source code is available for type java.io.InputStream; did you forget to inherit a required module?

Powodem tego jest to, że NyARToolkit (czyli biblioteka Java przeznaczona dla projektów Java) używa klas środowiska JRE, które nie są obsługiwane przez emulowane środowisko JRE GWT. Omówiliśmy to krótko w poprzednim poście.

W tym przypadku problem dotyczy InputStream i powiązanych klas IO. Tak się składa, że ​​nie musimy nawet używać większości tych klas, ale musimy dostarczyć kompilatorowi pewną implementację. Cóż, moglibyśmy zainwestować mnóstwo czasu w ręczne usuwanie tych odniesień ze źródła NyARToolkit, ale to byłoby szalone. GWT daje nam lepsze rozwiązanie: dostarczamy własne implementacje nieobsługiwanych klas za pomocą tagu <super-source> XML.

<super-source>

Zgodnie z opisem w oficjalnej dokumentacji:

Znacznik <super-source> nakazuje kompilatorowi ponowne zrootowanie ścieżki źródłowej. Jest to przydatne w przypadkach, gdy chcesz ponownie użyć istniejącego interfejsu Java API do projektu GWT, ale oryginalne źródło nie jest dostępne lub nie można go przetłumaczyć. Częstym powodem tego jest emulacja części środowiska JRE, która nie została zaimplementowana przez GWT.

Więc <super-source> jest dokładnie tym, czego potrzebujemy.

Możemy stworzyć katalog jre w projekcie GWT, w którym będziemy umieszczać nasze implementacje dla klas sprawiających nam problemy:

 java.io.FileInputStream java.io.InputStream java.io.InputStreamReader java.io.StreamTokenizer java.lang.reflect.Array java.nio.ByteBuffer java.nio.ByteOrder

Wszystkie te, z wyjątkiem java.lang.reflect.Array , są naprawdę nieużywane, więc potrzebujemy tylko głupich implementacji. Na przykład nasz FileInputStream brzmi następująco:

 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; } }

Instrukcja Window.alert w konstruktorze jest przydatna podczas programowania. Chociaż musimy być w stanie skompilować klasę, chcemy mieć pewność, że nigdy jej nie użyjemy, więc ostrzeże nas to w przypadku nieumyślnego użycia klasy.

java.lang.reflect.Array jest faktycznie używany przez kod, którego potrzebujemy, więc wymagana jest nie do końca głupia implementacja. Oto nasz kod:

 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; } }

Teraz jeśli umieścimy <super-source path="jre"/> w pliku modułu GWT_NyARToolkit.gwt.xml , możemy bezpiecznie skompilować i używać NyARToolkit w naszym projekcie!

Klejenie wszystkiego razem z GWT

Teraz jesteśmy w sytuacji, gdy mamy:

  • WebRTC, technologia umożliwiająca pobieranie strumienia z kamery internetowej i wyświetlanie go w tagu <video> .
  • WebGL, technologia zdolna do manipulowania grafiką przyspieszaną sprzętowo w HTML <canvas> .
  • NyARToolkit, biblioteka Java zdolna do robienia obrazu (jako tablicy pikseli), wyszukiwania znacznika i, jeśli zostanie znaleziony, dająca nam macierz transformacji, która w pełni definiuje pozycję znacznika w przestrzeni 3D.

Wyzwaniem jest teraz zintegrowanie wszystkich tych technologii razem.

Rzutowanie przestrzeni 3D na kamerę.

Nie będziemy zagłębiać się w to, jak to zrobić, ale podstawową ideą jest wykorzystanie obrazów wideo jako tła naszej sceny (tekstura zastosowana do „dalekiej” płaszczyzny na powyższym obrazku) i zbudowanie struktury danych 3D co pozwala nam rzutować ten obraz w przestrzeń kosmiczną przy użyciu wyników z NyARToolkit.

Ta konstrukcja daje nam odpowiednią strukturę do interakcji z biblioteką NyARToolkit do rozpoznawania znaczników i rysowania modelu 3D na górze sceny kamery.

Sprawienie, by strumień z kamery był użyteczny, jest nieco trudny. Dane wideo można narysować tylko do elementu <video> . Element HTML5 <video> jest nieprzezroczysty i nie pozwala nam bezpośrednio wyodrębnić danych obrazu, więc jesteśmy zmuszeni skopiować wideo do pośredniego <canvas> , wyodrębnić dane obrazu, przekształcić je w tablicę pikseli i na koniec wypchnij go do metody Sensor.update() . Następnie NyARToolkit może wykonać pracę polegającą na identyfikacji znacznika na obrazie i zwróceniu macierzy transformacji odpowiadającej jego pozycji w naszej przestrzeni 3D.

Dzięki tym elementom możemy umieścić syntetyczny obiekt dokładnie nad znacznikiem, w 3D, w strumieniu wideo na żywo! Dzięki wysokiej wydajności GWT mamy mnóstwo zasobów obliczeniowych, więc możemy nawet zastosować na płótnie niektóre efekty wideo, takie jak sepia lub rozmycie, zanim użyjemy ich jako tła dla sceny WebGL.

Poniższy skrócony kod opisuje istotę procesu:

 // 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 ... } ...

Dzięki tej technice możemy generować wyniki takie jak:

Wyniki działania aplikacji rzeczywistości rozszerzonej Picshare w przeglądarce.

Wyniki aplikacji Picshare w przeglądarce rozszerzonej rzeczywistości z wieloma znacznikami.

Jest to proces, którego użyliśmy do stworzenia Picshare , gdzie możesz wydrukować znacznik lub wyświetlić go na telefonie komórkowym i bawić się AR opartym na znacznikach w przeglądarce. Cieszyć się!

Uwagi końcowe

Picshare to dla nas w Jooink długoterminowy projekt dla zwierząt. Pierwsze wdrożenie miało miejsce kilka lat wstecz i nawet wtedy było na tyle szybkie, że robiło wrażenie. Pod tym linkiem możesz zobaczyć jeden z naszych wcześniejszych eksperymentów, skompilowany w 2012 roku i nigdy nie dotykany. Zauważ, że w przykładzie jest tylko jeden <video> . Pozostałe dwa okna to elementy <canvas> wyświetlające wyniki obróbki.

GWT był wystarczająco potężny nawet w 2012 roku. Wraz z wydaniem GWT 2.8 zyskaliśmy znacznie ulepszoną warstwę interoperacyjności z JsInterop, jeszcze bardziej zwiększając wydajność. Ponadto, aby uczcić wielu, zyskaliśmy również znacznie lepsze środowisko programistyczne i debugujące, tryb Super Dev. O tak, i obsługa Javy 8.

Nie możemy się doczekać GWT 3.0!