From df74b72f32682a0df1a533004997e73e57f4825d Mon Sep 17 00:00:00 2001 From: norangebit Date: Mon, 21 Jan 2019 21:52:49 +0100 Subject: [PATCH] init cloud anchor --- src/chapter3.6.md | 207 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 src/chapter3.6.md diff --git a/src/chapter3.6.md b/src/chapter3.6.md new file mode 100644 index 0000000..1ef748b --- /dev/null +++ b/src/chapter3.6.md @@ -0,0 +1,207 @@ +## Cloud anchors + +Un ulteriore funzionalità messa a disposizione da ARCore sono le *Cloud Anchors* che ci permette di salvare su un server remoto le ancore a cui sono agganciati i nodi. +Grazie a questa funzionalità è possibile salvare un'esperienza di realtà aumentata per un uso futuro o per condividere con altri utenti. + +In questo progetto verrà mostrato come sia possibile posizionare, tramite il device A, un vaso di fiori su una superficie piana, e vedere la stessa scena sul dispositivo B. + +### Configurazioni iniziali + +Per poter sfruttare le cloud anchors è necessario richiedere un API KEY sul sito di Google \url{https://console.cloud.google.com/apis/library/arcorecloudanchor.googleapis.com}. +Una volta ottenuta la chiave è necessario dichiararla nell'Android Manifest mediante il seguente codice xml. + +```xml + +``` + +Inoltre per tenere traccia dello stato dell'applicazione si è definita una classe enumerativa con cinque possibili valori. + +- `NONE`: non è presente alcuno oggetto nella scena ne se ne sta recuperando uno dal server. +- `HOSTING`: si sta caricando l'ancora sul server. +- `HOSTED`: l'ancora è stata caricata sul server. +- `RESOLVING`: si sta recuperando l'ancora dal server. +- `RESOLVED`: l'ancora è stata recuperata del server. + +### Attivazione delle cloud anchors + +Le cloud anchor di default sono disattivate e la loro attivazione può avvenire in due modi. + +- **Attivazione manuale**: + Con questa soluzione lo sviluppatore si occupa di creare una nuova configurazione della sessione di ARCore in cui le cloud anchors sono attivate e andare a sostituire questa nuova configurazione a quella di default. +- **Estensione dell'`ArFragment`**: + Viene creata una nuova classe che estende `ArFragment` in cui le cloud anchors sono attivate. + +Sebbene la prima soluzione possa sembrare più immediata, nasconde una grande insidia. +Infatti sarà compito dello sviluppatore andare a sovrascrivere i vari metodi che gestiscono il ciclo di vita dell'activity affinché non vengano ripristinate le impostazioni iniziali. +Mentre con il secondo metodo sarà Sceneform a gestire il ciclo di vita al posto nostro. + +Per questo motivo è stata creata la classe `CloudArFragment` in cui è stata sovrascritta la funzione `getSessionConfiguration` in modo da attivare le cloud anchors. + +```kotlin +class CloudArFragment: ArFragment(){ + override fun getSessionConfiguration( + session: Session? + ): Config { + val config = super.getSessionConfiguration(session) + config.cloudAnchorMode = Config + .CloudAnchorMode.ENABLED + return config + } +} +``` + +Inoltre bisogna modificare anche il file di layout affinché non utilizzi più l'`ArFragment`, ma il `CloudArFragment`. + +### Cloud Anchor Helper + +Quando viene caricata un'ancora sul server viene associata ad essa un valore alfanumerico che ci permette di identificarla univocamente. +Dato che il codice risulta essere molto lungo e quindi difficile da ricopiare, si è scelto di appoggiarsi al servizio *firestore * di Firebase per creare una relazione uno tra l'UUID e un valore numerico intero. + +Queste operazioni avvengono mediante la classe `CloudAnchorHelper` che fornisce due metodi principali `getShortCode` e `getCloudAnchorId`. + +```kotlin +fun getShortCode(cloudAnchorId: String): Int { + fireStoreDb.collection(COLLECTION) + .document(DOCUMENT) + .set( + mapOf(Pair(nextCode.toString(), cloudAnchorId)), + SetOptions.merge() + ) + uploadNextCode(nextCode+1) + + return nextCode++ +} +``` + +```kotlin +fun getCloudAnchorId( + shortCode: Int, + onSuccess: (String) -> Unit +) { + fireStoreDb.collection(COLLECTION) + .document(DOCUMENT) + .get() + .addOnSuccessListener { + val uuid=it.data.get(shortCode.toString()) as String + onSuccess(uuid) + } +} +``` + +Il primo metodo riceve in ingresso l'UUID dell'ancora e lo aggiunge al database usando come chiave un numero intero. +Mentre il secondo metodo dato il codice intero recupera l'identificativo dell'ancora e svolge su di esso le operazioni specificate nella *lambda expression* `onSuccess`. + +### Aggiunta del modello + +L'aggiunta del modello avviene attraverso la funzione `addModel`, che opera in modo simile a quanto visto fin'ora, con l'unica differenza che l'aggiunta è consentita solo se l'applicazione si trova nello stato `NONE`. +Inoltre la creazione dell'ancora è delegata al metodo `hostCloudAnchors` che si occupa anche dell'upload di quest'ultima sul server. + +```kotlin +private fun addModel( + hitResult: HitResult, + plane: Plane, + motionEvent: MotionEvent +) { + if (cloudAnchorState != CloudAnchorState.NONE) + return + + cloudAnchor = arFragment.arSceneView.session + .hostCloudAnchor(hitResult.createAnchor()) + + cloudAnchorState = CloudAnchorState.HOSTING + + buildRenderable(this, Uri.parse("model.sfb")) { + addTransformableNodeToScene( + arFragment, + cloudAnchor ?: return@buildRenderable, + it + ) + } +} +``` + +### Check Hosting + +Il metodo `checkCloudAnchor` viene eseguito ogni qual volta viene aggiornata la scena, e in base allo stato dell'applicazione vengono eseguite determinate operazioni. + +```kotlin +private fun checkCloudAnchor(frameTime: FrameTime) { + if (cloudAnchorState != CloudAnchorState.HOSTING + && cloudAnchorState != CloudAnchorState.RESOLVING + ) + return + + val cloudState = cloudAnchor?.cloudAnchorState?:return + + if (cloudState.isError) { + toastError() + cloudAnchorState = CloudAnchorState.NONE + return + } + + if (cloudState != Anchor.CloudAnchorState.SUCCESS) + return + + if (cloudAnchorState == CloudAnchorState.HOSTING) + checkHosting() + else + checkResolving() +} +``` + +La funzione `checkHosting` si occupa di notificare all'utente il codice numerico associato all'ancora e di cambiare lo stato dell'applicazione. + +```kotlin +private fun checkHosting() { + val cAnchor = cloudAnchor ?: return + + val shortCode = cloudAnchorsHelper + .getShortCode(cAnchor.cloudAnchorId) + + Toast + .makeText( + this, + "Anchor hosted with code $shortCode", + Toast.LENGTH_LONG + ) + .show() + + cloudAnchorState = CloudAnchorState.HOSTED +} +``` + +### Resolving dell'ancora + +L'utente può ripristinare un ancora premendo sul pulsante *resolve*. +Il listener associato a questo evento è racchiuso nella funzione `onResolve` che a sua volta mostra all'utente un dialog in cui può inserire il codice dell'ancora da ripristinare. + +```kotlin +fun onResolveClick(view: View) { + if (cloudAnchor != null) + return + + val dialog = ResolveDialogFragment() + dialog.setOkListener(this::onResolveOkPressed) + dialog.show(supportFragmentManager, "Resolve") +} +``` + +Alla conferma da parte dell'utente viene eseguito il metodo `onResolveOkPressed` che converte il codice numerico nell'UUID dell'ancora e da questo ripristina il nodo nella scena. + +```kotlin +private fun onResolveOkPressed(dialogValue: String) { + val shortCode = dialogValue.toInt() + cloudAnchorsHelper.getCloudAnchorId(shortCode) { + cloudAnchor = arFragment.arSceneView.session + .resolveCloudAnchor(it) + + buildRenderable(this, Uri.parse("model.sfb")) { + val anchor = cloudAnchor ?: return@buildRenderable + addTransformableNodeToScene(arFragment, anchor, it) + cloudAnchorState = CloudAnchorState.RESOLVING + } + } +} +```