Cum deblochează GWT realitatea mărită în browserul dvs
Publicat: 2022-03-11În postarea noastră anterioară despre GWT Web Toolkit, am discutat despre punctele forte și caracteristicile GWT, care, pentru a ne aminti ideea generală, ne permite să transpilăm codul sursă Java în JavaScript și să amestecăm bibliotecile Java și JavaScript fără probleme. Am observat că JavaScript generat de GWT este optimizat dramatic.
În postarea de astăzi, am dori să aprofundăm puțin și să vedem setul de instrumente GWT în acțiune. Vom demonstra cum putem profita de GWT pentru a construi o aplicație deosebită: o aplicație web de realitate augmentată (AR) care rulează în timp real, complet în JavaScript, în browser.
În acest articol, ne vom concentra asupra modului în care GWT ne oferă posibilitatea de a interacționa cu ușurință cu multe API-uri JavaScript, cum ar fi WebRTC și WebGL, și ne permite să valorificăm o bibliotecă Java mare, NyARToolkit, care nu a fost destinată niciodată să fie utilizată în browser. Vom arăta cum GWT ne-a permis echipei mele și cu mine de la Jooink să punem toate aceste piese împreună pentru a crea proiectul nostru de companie, Picshare , o aplicație AR bazată pe markeri pe care o puteți încerca în browser chiar acum.
Această postare nu va fi o prezentare completă a modului de construire a aplicației, ci va prezenta mai degrabă utilizarea GWT pentru a depăși provocările aparent copleșitoare cu ușurință.
Prezentare generală a proiectului: de la realitate la realitate augmentată
Picshare folosește realitatea augmentată bazată pe markeri. Acest tip de aplicație AR caută în scenă un marker : un model geometric specific, ușor de recunoscut, ca acesta. Markerul oferă informații despre poziția și orientarea obiectului marcat, permițând software-ului să proiecteze peisaje 3D suplimentare în imagine într-un mod realist. Pașii de bază în acest proces sunt:
- Accesați camera: atunci când aveți de-a face cu aplicații desktop native, sistemul de operare oferă acces I/O la mare parte din hardware-ul dispozitivului. Nu este același lucru când avem de-a face cu aplicații web. Browserele au fost construite pentru a fi un fel de „sandbox” pentru codul JavaScript descărcat de pe net și inițial nu au fost destinate să permită site-urilor web să interacționeze cu majoritatea hardware-ului dispozitivelor. WebRTC depășește această barieră folosind funcțiile de captură media HTML5, permițând browserului să acceseze, printre altele, camera dispozitivului și fluxul său.
- Analizați fluxul video: avem fluxul video... acum ce? Trebuie să analizăm fiecare cadru pentru a detecta markeri și să calculăm poziția markerului în lumea 3D reconstruită. Această sarcină complexă este afacerea NyARToolkit.
- Măriți videoclipul: în sfârșit, dorim să afișăm videoclipul original cu obiecte 3D sintetice adăugate. Folosim WebGL pentru a desena scena finală, mărită pe pagina web.
Profitați de API-urile HTML5 cu GWT
Utilizarea API-urilor JavaScript precum WebGL și WebRTC permite interacțiuni neașteptate și neobișnuite între browser și utilizator.
De exemplu, WebGL permite grafică accelerată hardware și, cu ajutorul specificației matricei tipizate, permite motorului JavaScript să execute scrierea numerelor cu performanță aproape nativă. În mod similar, cu WebRTC, browserul este capabil să acceseze fluxurile video (și alte date) direct din hardware-ul computerului.
WebGL și WebRTC sunt ambele biblioteci JavaScript care trebuie încorporate în browserul web. Majoritatea browserelor HTML5 moderne vin cu suport cel puțin parțial pentru ambele API-uri (după cum puteți vedea aici și aici). Dar cum putem valorifica aceste instrumente în GWT, care este scris în Java? După cum sa discutat în postarea anterioară, stratul de interoperabilitate al lui GWT, JsInterop (lansat oficial în GWT 2.8) face ca acest lucru să fie ușor.
Utilizarea JsInterop cu GWT 2.8 este la fel de ușoară ca și adăugarea -generateJsInteropExports
ca argument la compilator. Adnotările disponibile sunt definite în pachetul jsinterop.annotations
, inclus în gwt-user.jar
.
WebRTC
De exemplu, cu o muncă minimă de codare, utilizarea getUserMedia
de la WebRTC pe Chrome cu GWT devine la fel de simplă ca și scrisul:
Navigator.webkitGetUserMedia( configs, stream -> video.setSrc( URL.createObjectURL(stream) ), e -> Window.alert("Error: " + e) );
Unde clasa Navigator
poate fi definită după cum urmează:
@JsType(namespace = JsPackage.GLOBAL, isNative = true, name="navigator") final static class Navigator { public static native void webkitGetUserMedia( Configs configs, SuccessCallback success, ErrorCallback error); }
Interesantă este definirea interfețelor ErrorCallback
SuccessCallback
ambele implementate prin expresia lambda de mai sus și definite în Java prin intermediul adnotării @JsFunction
:
@JsFunction public interface SuccessCallback { public void onMediaSuccess(MediaStream stream); } @JsFunction public interface ErrorCallback { public void onError(DomException error); }
În cele din urmă, definiția URL
-ului clasei este aproape identică cu cea a Navigator
și, în mod similar, clasa Configs
poate fi definită prin:
@JsType(namespace = JsPackage.GLOBAL, isNative = true, name="Object") public static class Configs { @JsProperty public native void setVideo(boolean getVideo); }
Implementarea efectivă a tuturor acestor funcționalități are loc în motorul JavaScript al browserului.
Puteți găsi codul de mai sus pe GitHub aici.
În acest exemplu, de dragul simplității, este utilizat API-ul navigator.getUserMedia()
depreciat, deoarece este singurul care funcționează fără completare polivalentă pe versiunea stabilă actuală a Chrome. Într-o aplicație de producție, putem folosi adapter.js pentru a accesa fluxul prin noul navigator.mediaDevices.getUserMedia()
API, în mod uniform în toate browserele, dar acest lucru depășește scopul prezentei discuții.
WebGL
Utilizarea WebGL de la GWT nu este mult diferită în comparație cu utilizarea WebRTC, dar este puțin mai obositoare din cauza complexității intrinseci a standardului OpenGL.
Abordarea noastră aici o oglindește pe cea urmată în secțiunea anterioară. Rezultatul împachetarii poate fi văzut în implementarea GWT WebGL utilizată în Picshare , care poate fi găsită aici, iar un exemplu de rezultate produse de GWT poate fi găsit aici.
Activarea WebGL în sine nu ne oferă de fapt capacitatea de grafică 3D. După cum scrie Gregg Tavares:
Ceea ce mulți oameni nu știu este că WebGL este de fapt un API 2D, nu un API 3D.
Aritmetica 3D trebuie efectuată de un alt cod și transformată în imaginea 2D pentru WebGL. Există câteva biblioteci GWT bune pentru grafica 3D WebGL. Preferatul meu este Parallax, dar pentru prima versiune de Picshare am urmat o cale mai „do-it-yourself”, scriind o bibliotecă mică pentru redarea rețelelor 3D simple. Biblioteca ne permite să definim o cameră de perspectivă și să gestionăm o scenă de obiecte. Simțiți-vă liber să verificați, aici.
Compilarea bibliotecilor Java terțe cu GWT
NyARToolkit este un port pur-Java al ARToolKit, o bibliotecă de software pentru construirea de aplicații de realitate augmentată. Portul a fost scris de dezvoltatorii japonezi de la Nyatla. Deși ARToolKit original și versiunea Nyatla s-au oarecum diferit de la portul original, NyARToolkit este încă întreținut și îmbunătățit activ.
AR bazat pe markeri este un domeniu specializat și necesită competențe în viziune computerizată, procesare digitală a imaginilor și matematică, așa cum se vede aici:
Reproducere din documentația ARToolKit.
Reproducere din documentația ARToolKit.
Toți algoritmii utilizați de setul de instrumente sunt documentați și bine înțeleși, dar rescrierea lor de la zero este un proces lung și predispus la erori, așa că este de preferat să folosiți un set de instrumente existent și dovedit, cum ar fi ARToolKit. Din păcate, atunci când vizați web, nu există așa ceva disponibil. Cele mai puternice seturi de instrumente avansate nu au implementare în JavaScript, un limbaj care este folosit în principal pentru manipularea documentelor și datelor HTML. Aici GWT își dovedește puterea neprețuită, permițându-ne pur și simplu să transpilăm NyARToolkit în JavaScript și să-l folosim într-o aplicație web cu foarte puține bătăi de cap.
Compilarea cu GWT
Deoarece un proiect GWT este în esență un proiect Java, utilizarea NyARToolkit este doar o chestiune de importare a fișierelor sursă în calea sursă. Cu toate acestea, rețineți că, deoarece transpilarea codului GWT în JavaScript se face la nivel de cod sursă, aveți nevoie de sursele NyARToolkit și nu doar de un JAR cu clasele compilate.
Biblioteca folosită de Picshare poate fi găsită aici. Depinde doar de pachetele găsite în lib/src
și lib/src.markersystem
din compilația NyARToolkit arhivată aici. Trebuie să copiem și să importam aceste pachete în proiectul nostru GWT.

Ar trebui să păstrăm aceste pachete de la terți separat de propria noastră implementare, dar pentru a continua cu „GWT-ization” a NyARToolkit trebuie să furnizăm un fișier de configurare XML care să informeze compilatorul GWT unde să caute sursele. În pachetul jp.nyatla.nyartoolkit
, adăugăm fișierul NyARToolkit.gwt.xml
<module> <source path="core" /> <source path="detector" /> <source path="nyidmarker" /> <source path="processor" /> <source path="psarplaycard" /> <source path="markersystem" /> </module>
Acum, în pachetul nostru principal, com.jooink.gwt.nyartoolkit
, creăm fișierul de configurare principal, GWT_NyARToolKit.gwt.xml
și instruim compilatorului să includă sursa lui Nyatla în calea clasei prin moștenirea din fișierul său XML:
<inherits name='jp.nyatla.nyartoolkit.NyARToolkit'/>
Destul de ușor, de fapt. În cele mai multe cazuri, asta ar fi tot ce trebuie, dar, din păcate, nu am terminat încă. Dacă încercăm să compilam sau să executăm prin Super Dev Mode în această etapă, întâlnim o eroare care spune, destul de surprinzător:
No source code is available for type java.io.InputStream; did you forget to inherit a required module?
Motivul pentru aceasta este că NyARToolkit (adică o bibliotecă Java destinată proiectelor Java) utilizează clase ale JRE care nu sunt acceptate de JRE emulat al GWT. Am discutat pe scurt despre acest lucru în postarea anterioară.
În acest caz, problema este cu InputStream
și cu clasele IO aferente. După cum se întâmplă, nici măcar nu trebuie să folosim majoritatea acestor clase, dar trebuie să oferim o implementare compilatorului. Ei bine, am putea investi o mulțime de timp în eliminarea manuală a acestor referințe din sursa NyARToolkit, dar ar fi o nebunie. GWT ne oferă o soluție mai bună: să oferim propriile noastre implementări ale claselor neacceptate prin eticheta XML <super-source>
.
<super-source>
După cum este descris în documentația oficială:
Eticheta
<super-source>
indică compilatorului să rerooteze o cale sursă. Acest lucru este util pentru cazurile în care doriți să reutilizați un API Java existent pentru un proiect GWT, dar sursa originală nu este disponibilă sau nu poate fi tradusă. Un motiv comun pentru aceasta este emularea unei părți a JRE neimplementată de GWT.
Deci <super-source>
este exact ceea ce avem nevoie.
Putem crea un director jre
în proiectul GWT, unde putem pune implementările noastre pentru clasele care ne creează probleme:
java.io.FileInputStream java.io.InputStream java.io.InputStreamReader java.io.StreamTokenizer java.lang.reflect.Array java.nio.ByteBuffer java.nio.ByteOrder
Toate acestea, cu excepția java.lang.reflect.Array
, sunt cu adevărat neutilizate, așa că avem nevoie doar de implementări stupide. De exemplu, FileInputStream
nostru arată după cum urmează:
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; } }
Declarația Window.alert
din constructor este utilă în timpul dezvoltării. Deși trebuie să putem compila clasa, vrem să ne asigurăm că nu o folosim niciodată, așa că acest lucru ne va alerta în cazul în care clasa este folosită din neatenție.
java.lang.reflect.Array
este de fapt folosit de codul de care avem nevoie, așa că este necesară o implementare care nu este complet stupidă. Acesta este codul nostru:
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; } }
Acum, dacă plasăm <super-source path="jre"/>
în fișierul modulului GWT_NyARToolkit.gwt.xml
, putem compila și folosi în siguranță NyARToolkit în proiectul nostru!
Lipirea totul împreună cu GWT
Acum suntem în situația de a avea:
- WebRTC, o tehnologie capabilă să preia un flux de la camera web și să îl afișeze într-o etichetă
<video>
. - WebGL, o tehnologie capabilă să manipuleze grafica accelerată de hardware într-o
<canvas>
HTML. - NyARToolkit, o bibliotecă Java capabilă să ia o imagine (ca o matrice de pixeli), să caute un marker și, dacă este găsit, să ne ofere o matrice de transformare care definește complet poziția markerului în spațiul 3D.
Provocarea acum este de a integra toate aceste tehnologii împreună.
Nu vom intra în profunzime despre cum să realizam acest lucru, dar ideea de bază este să folosim imaginile video ca fundal al scenei noastre (o textură aplicată la planul „departe” din imaginea de mai sus) și să construim o structură de date 3D. permițându-ne să proiectăm această imagine în spațiu folosind rezultatele de la NyARToolkit.
Această construcție ne oferă structura potrivită pentru a interacționa cu biblioteca NyARToolkit pentru recunoașterea markerului și pentru a desena modelul 3D deasupra scenei camerei.
A face ca fluxul camerei să fie utilizabil este puțin complicat. Datele video pot fi desenate numai într-un element <video>
. Elementul HTML5 <video>
este opac și nu ne lasă să extragem datele imaginii în mod direct, așa că suntem forțați să copiem videoclipul într-o <canvas>
intermediară, să extragem datele imaginii, să le transformăm într-o matrice de pixeli și, în final împingeți-l la metoda Sensor.update()
de la NyARToolkit. Apoi NyARToolkit poate face munca de identificare a markerului din imagine și de a returna o matrice de transformare corespunzătoare poziției sale în spațiul nostru 3D.
Cu aceste elemente, putem așeza un obiect sintetic exact peste marker, în 3D, în fluxul video live! Datorită performanței ridicate a GWT, avem o mulțime de resurse de calcul, astfel încât putem chiar să aplicăm unele efecte video pe pânză, cum ar fi sepia sau estompare, înainte de a le folosi ca fundal pentru scena WebGL.
Următorul cod prescurtat descrie nucleul procesului:
// 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 ... } ...
Cu această tehnică, putem genera rezultate precum acesta:
Acesta este procesul pe care l-am folosit pentru a crea Picshare , în care sunteți invitat să imprimați un marker sau să îl afișați pe mobil și să vă jucați cu AR bazat pe markeri în browser. Bucurați-vă!
Observații finale
Picshare este un proiect pe termen lung pentru animale de companie pentru noi, la Jooink. Prima implementare datează de câțiva ani și chiar și atunci a fost suficient de rapidă pentru a fi impresionantă. La acest link puteți vedea unul dintre experimentele noastre anterioare, compilat în 2012 și niciodată atins. Rețineți că în eșantion există doar un <video>
. Celelalte două ferestre sunt elemente <canvas>
care afișează rezultatele procesării.
GWT a fost suficient de puternic chiar și în 2012. Odată cu lansarea GWT 2.8, am câștigat un nivel de interoperabilitate mult îmbunătățit cu JsInterop, sporind și mai mult performanța. De asemenea, pentru sărbătorirea multora, am câștigat și un mediu de dezvoltare și depanare mult mai bun, Super Dev Mode. Da, și suport Java 8.
Așteptăm cu nerăbdare GWT 3.0!