diff --git a/src/chapter3.0.md b/src/chapter3.0.md new file mode 100644 index 0000000..32b2184 --- /dev/null +++ b/src/chapter3.0.md @@ -0,0 +1,31 @@ +# Progetti d'esempio + +Per poter realizzare delle applicazioni mediante ARCore e Sceneform sono necessarie una serie di configurazioni iniziali. + +Requisito necessario al funzionamento di ARCore è una versione di Android uguale o superiore ad Android 7.0 Nougat(API level 24). +Inoltre se si sta lavorando su un progetto con API level minore di 26 è necessario esplicitare il supporto a Java 8 andando a modificare file `app/build.gradle`. + +```gradle +android { + ... + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + ... +} +``` + +Sempre nel file per il build del progetto è necessario aggiungere la dipendenza di Sceneform. + +```gradle +implementation "com.google.ar.sceneform.ux:sceneform-ux:1.6.0" +``` + +Inoltre nell'Android Manifest[^manifest] va dichiarato l'utilizzo del permesso della fotocamera[^camera] e l'utilizzo di ARCore[^arcore]. + +[^manifest]: File in cui vengono dichiarate tutte caratteristiche di un'applicazione Android, tra cui anche i permessi. + +[^camera]: Lo sviluppatore deve solo dichiarare l'utilizzo del permesso, la richiesta di concessione è gestita in automatico da Sceneform. + +[^arcore]: L'utilizzo di ARCore deve essere dichiarata in quanto non tutti i dispositivi lo supportano. diff --git a/src/chapter3.1.md b/src/chapter3.1.md new file mode 100644 index 0000000..cc89b41 --- /dev/null +++ b/src/chapter3.1.md @@ -0,0 +1,128 @@ +## Augmented images + +Nel primo progetto d'esempio si è affrontato un classico problema di AR marker based, ovvero il riconoscimento di un'immagine preimpostata e il conseguente sovrapponimento di un oggetto virtuale. +Nel caso specifico si vuole riconoscere una foto del pianeta terra e sostituirvi un modello tridimensionale di essa. + +### Aggiunta del modello + +Il modello tridimensionale della terra è stato recuperato dal sito `poly.google.com`, che funge da repository per modelli 3D. +La scelta del modello è stata dettata sia del formato[^format] in cui era disponibile, sia dalla licenza con cui veniva distribuito. +Una volta ottenuto il modello è stato salvato nella cartella *"sampledata"*, il cui contenuto sarà usato solo in fase di progettazione. + +L'importazione del modello all'interno del progetto di Android Studio è stato effettuato mediante l'utilizzo del plug-in *Google Sceneform Tools*, che si occupa sia di convertire il modello nel formato di Sceneform, sia di aggiornare il file `build.gradle` affinché sia incluso nell'APK[^apk] finale. + +### Creazione del database + +Il database contenente tutte le immagini che si desidera far riconosce all'applicazione, può essere creato sia a priori, sia a tempo di esecuzione. +Per la prima soluzione Google mette a disposizione *The arcoreimag tool*, un software a linea di comando, che oltre a creare il database, si occupa anche di valutare l'immagine. + +Dato che nel caso specifico si vuole far riconoscere un'unica immagine, si è optato per la generazione del database a runtime. +In particolare quest'operazione avviene mediante la funzione `setupAugmentedImageDb`. + +```kotlin +private fun setupAugmentedImageDb (config: Config): Boolean { + val image = loadImage(IMAGE_FILE_NAME) ?: return false + + val augmentedImageDb = AugmentedImageDatabase(session) + augmentedImageDb.addImage(IMAGE_NAME, image) + config.augmentedImageDatabase = augmentedImageDb + return true +} +``` + +### Riconoscimento dell'immagine + +Il riconoscimento dell'immagine non può avvenire mediate l'utilizzo di una callback in quanto ARCore non permette di registrare un listener all'evento. +Risulta dunque evidente che la verifica dell'avvenuto match sarà delegata allo sviluppatore. + +Per fare ciò si è usato il metodo `addOnUpdateListener` dell'oggetto `Scene`, che permette di eseguire un pezzo di codice ogni qual volta la scena viene aggiornata. + +```kotlin +override fun onCreate(savedInstanceState: Bundle?) { + //... + + arSceneView.scene.addOnUpdateListener(this::detectAndPlaceAugmentedImage) + + //... +} +``` + +Dove la funzione `detectAndPlaceAugmentedImage` si occupa di verificare la presenza di un match e nel caso di un riscontro positivo, dell'aggiunta dell'oggetto virtuale alla scena. + +```kotlin +private fun detectAndPlaceAugmentedImage(frameTime: FrameTime) { + if (isModelAdded) + return + + val augmentedImage = arSceneView.arFrame + .getUpdatedTrackables(AugmentedImage::class.java) + .filter { isTrackig(it) } + .find { it.name.contains(IMAGE_NAME) } + ?: return + + val augmentedImageAnchor = augmentedImage.createAnchor(augmentedImage.centerPose) + + buildRenderable(this, Uri.parse(MODEL_NAME)) { + addTransformableNodeToScene( + arFragment, + augmentedImageAnchor, + it + ) + } + + isModelAdded = true +} +``` + +Il settaggio del flag `isModelAdded` al valore booleano di vero, si rende necessario al fine di evitare l'aggiunta incontrollata di nuovi modelli alla medesima immagine. + +### Rendering del modello + +Il rendering del modello avviene attraverso la funzione `buildRenderable` che a sua volta chiama la funzione di libreria `ModelRenderable.builder()`. +Poiché quest'ultima è un operazione onerosa viene restituito un `Future`[^future] che racchiude il `Renderable` vero e proprio. +L'interazione con l'oggetto concreto avviene mediante una callback che è possibile specificare attraverso il metodo `thenAccept`. + +```kotlin +fun buildRenderable( + context: Context, + model: Uri, + onSuccess: (renderable: Renderable) -> Unit +) { + ModelRenderable.builder() + .setSource(context, model) + .build() + .thenAccept(onSuccess) + .exceptionally { + Log.e("SCENEFORM", "unable to load model", it) + return@exceptionally null + } +} +``` + +### Aggiunta dell'oggetto virtuale nella scena + +L'ultima operazione da compiere è l'aggiunta del modello renderizzato alla scena. +Questa operazione avviene attraverso la funzione `addTrasformableNodeToScene` che si occupa di creare un'ancora in corrispondenza del punto reale d'interesse. +A partire da quest'ancora viene creato un nodo che racchiude l'oggetto renderizzato. +Inoltre nel caso specifico si è usato un `TransformabelNode`, in modo da concedere all'utente la possibilità di ridimensionare o ruotare il modello. + +```kotlin +fun addTransformableNodeToScene( + arFragment: ArFragment, + anchor: Anchor, + renderable: Renderable +) { + val anchorNode = AnchorNode(anchor) + val transformableNode = TransformableNode(arFragment.transformationSystem) + transformableNode.renderable = renderable + transformableNode.setParent(anchorNode) + arFragment.arSceneView.scene.addChild(anchorNode) + transformableNode.select() +} +``` + +[^format]: Attualmente sono supportati solo modelli OBJ, FBX e gLTF. + +[^apk]: Formato delle applicazioni Android. + +[^future]: In informatica con il termine *future*, o *promise*, *delay* e *deferred*, si indica un tecnica che permette di sincronizzare l'esecuzione di un programma concorrente. diff --git a/src/chapter3.2.md b/src/chapter3.2.md new file mode 100644 index 0000000..822af0c --- /dev/null +++ b/src/chapter3.2.md @@ -0,0 +1,93 @@ +## Runtime fetching models + +Nella seconda applicazione d'esempio viene mostrato come sia possibile recuperare i modelli da renderizzare anche durante l'esecuzione dell'applicazione. +Questa funzione risulta particolarmente utile quando si deve rilasciare un'applicazione che sfrutta numerosi modelli e non si vuole appesantire eccessivamente il volume del file *APK*. +Inoltre concede maggiore libertà allo sviluppatore in quanto è possibile aggiungere nuovi modelli, o aggiornare quelli vecchi, senza dover operare sull'applicazione, ma lavorando esclusivamente lato server. + +In questo caso specifico l'applicazione dovrà riconosce uno o più piani e in seguito ad un tocco dell'utente su di essi, mostrare un modello di *Andy*, la mascotte di Android. + +Per quest'applicazione oltre alle configurazioni già viste in precedenza è necessario aggiungere una nuova dipendenza che include le funzioni necessarie per il fetching del modello. + +```gradle + implementation 'com.google.ar.sceneform:assets:1.6.0' +``` + +Inoltre nell'Android Manifest bisogna aggiungere il permesso per accedere alla rete. + +### Interazione con l'utente + +L'interazione con l'utente avviene mediante un tocco sul display in corrispondenza di un piano. +Sceneform ci permette di personalizzare il comportamento al verificarsi di questo evento tramite il metodo `setOnTapArPlaneListener`. + +```kotlin +override fun onCreate(savedInstanceState: Bundle?) { + //... + arFragment.setOnTapArPlaneListener(this::fetchAndPlaceModel) + //... +} +``` + +Dove la funzione `fetchAndPlaceModel` si occupa di recuperare il modello e renderizzarlo. + +```kotlin +private fun fetchAndPlaceModel( + hitResult: HitResult, + plane: Plane, + motionEvent: MotionEvent +) { + val modelUri = Uri.parse(MODEL_SOURCE) + val fetchedModel = fetchModel(this, modelUri) + buildRenderable(this, fetchedModel, modelUri) { + addTransformableNodeToScene( + arFragment, + hitResult.createAnchor(), + it + ) + } +} +``` + +### Fetching del model + +Il recupero del modello avviene attraverso la funzione `fetchModel`, che a sua volta chiama la funzione di libreria `RenderableSource.builder()`. + +```kotlin +fun fetchModel( + context: Context, + source: Uri +) : RenderableSource { + return RenderableSource.builder() + .setSource(context, source, RenderableSource.SourceType.GLTF2) + .setRecenterMode(RenderableSource.RecenterMode.ROOT) + .build() +} +``` + +Attualmente[^sceneform-1.6] Sceneform supporta unicamente il fetching di modelli gLTF. + +### Rendering e aggiunta del modello + +Il rendering del modello avviene tramite la funzione `buildRenderable`, che riprende in buona parte quella vista precedentemente, con la differenza che in questo caso deve essere passato anche il modello recuperato. + +```kotlin +fun buildRenderable( + context: Context, + model: RenderableSource, + modelUri: Uri, + onSuccess: (renderable: Renderable) -> Unit +) { + ModelRenderable.builder() + .setRegistryId(modelUri) + .setSource(context, model) + .build() + .thenAccept(onSuccess) + .exceptionally { + Log.e("SCENEFORM", "unable to load model", it) + return@exceptionally null + } +} +``` + +Infine l'aggiunta del modello renderizzato alla scena avviene mediante la medesima funzione `addTransformableNodeToScene` vista in precedenza. + +[^sceneform-1.6]: Sceneform 1.6.0.