impaginazione
This commit is contained in:
parent
a41ac06658
commit
f3ed17dba7
@ -1,22 +1,20 @@
|
|||||||
## Animazioni e movimento
|
## Animazioni e movimento
|
||||||
|
|
||||||
Negli esempi discussi fino a questo momento sono stati usati unicamente asset tridimensionali statici.
|
Negli esempi discussi fino a questo momento sono stati usati unicamente asset tridimensionali statici.
|
||||||
Gli oggetti virtuali che di volta in volta sono stati integrati nel mondo reale non erano dotati di animazioni, né erano in grado di muoversi all'interno dell'ambiente reale circostante.
|
Gli oggetti virtuali che di volta in volta sono stati integrati nel mondo reale non erano né dotati di animazioni, né erano in grado di muoversi all'interno dell'ambiente reale circostante.
|
||||||
La scelta di utilizzare modelli statici è stata dettata da un lato dalla filosofia *kiss*[^kiss], e quindi di concentrassi unicamente sull'aspetto rilevante del progetto, dall'altro da un supporto quasi inesistente alle animazioni e al movimento.
|
La scelta di utilizzare modelli statici è stata dettata da un lato dalla filosofia *kiss*[^kiss], e quindi di concentrassi unicamente sull'aspetto rilevante del progetto, dall'altro da un supporto quasi inesistente alle animazioni e al movimento.
|
||||||
|
|
||||||
Come abbiamo avuto modo di vedere nel secondo capitolo, queste lacune sono da imputare principalmente alla libreria grafica.
|
Come abbiamo avuto modo di vedere nel secondo capitolo, queste lacune sono da imputare principalmente alla libreria grafica.
|
||||||
Infatti sia per le animazioni, sia per la gestione del movimento, Sceneform non offre alcun supporto nativo e diretto.
|
Infatti sia per le animazioni, sia per la gestione del movimento, Sceneform non offre alcun supporto nativo o diretto.
|
||||||
Nonostante ciò è possibile aggirare il problema tramite opportuni escamotage, anche se bisogna tenere in conto che i risultati sono modesti e macchinosi da raggiungere.
|
Nonostante ciò è possibile aggirare il problema tramite opportuni escamotage, anche se bisogna tenere in conto che i risultati sono generalmente modesti e macchinosi da raggiungere.
|
||||||
|
|
||||||
Nelle sezioni seguenti verrano analizzate in dettaglio le possibili soluzioni.
|
|
||||||
|
|
||||||
### Animazioni
|
### Animazioni
|
||||||
|
|
||||||
L'utilizzo di oggetti animati oltre ad essere un orpello grafico diventa, in molti casi, un punto principale nel processo di sviluppo.
|
L'utilizzo di oggetti animati oltre ad essere un orpello grafico diventa, in molti casi, un aspetto fondamentale nel processo di sviluppo.
|
||||||
Questo è ancora più vero nello sviluppo di applicazioni AR su smartphone che, per forza di cose, hanno nell'utenza consumers lo sbocco naturale.
|
Questo è ancora più vero nello sviluppo di applicazioni AR su smartphone che, per forza di cose, hanno nell'utenza consumers lo sbocco naturale.
|
||||||
Il mancato utilizzo di un'animazione potrebbe segnare in modo permanete l'esperienza utente e quindi determinare il fallimento del progetto.
|
Il mancato utilizzo di un'animazione potrebbe segnare in modo permanete l'esperienza utente e quindi determinare il fallimento del progetto.
|
||||||
|
|
||||||
Non a caso uno dei problemi più discussi nell'issues tracker di Sceneform su GitHub[@googlear:Animated3DObjects:2019], sia proprio la totale mancanza di supporto alle animazioni.
|
Non a caso uno dei problemi più discussi nell'issues tracker di Sceneform su GitHub[@googlear:Animated3DObjects:2019], è proprio la totale mancanza di supporto alle animazioni.
|
||||||
Sebbene ci siano stati dei lavori in questo senso ed una prima contabilità con i modelli animati FBX si stata aggiunta alla code base, ad oggi non è possibile utilizzare questa funzione, in quanto è in fase di testing per il solo personale interno.
|
Sebbene ci siano stati dei lavori in questo senso ed una prima contabilità con i modelli animati FBX si stata aggiunta alla code base, ad oggi non è possibile utilizzare questa funzione, in quanto è in fase di testing per il solo personale interno.
|
||||||
|
|
||||||
In attesa di un rilascio al pubblico, l'unica via percorribile è quella presentata dalla stessa Google durante un codelab[@googlear:ChromaKey:2019].
|
In attesa di un rilascio al pubblico, l'unica via percorribile è quella presentata dalla stessa Google durante un codelab[@googlear:ChromaKey:2019].
|
||||||
@ -24,8 +22,8 @@ La soluzione consiste nel renderizzare un schermo trasparente nel mondo reale e
|
|||||||
Affinché l'*illusione* riesca è necessario usare un video che sfrutti il *chroma key*[^chroma-key], in questo modo l'integrazione con il mondo reale risulta migliore.
|
Affinché l'*illusione* riesca è necessario usare un video che sfrutti il *chroma key*[^chroma-key], in questo modo l'integrazione con il mondo reale risulta migliore.
|
||||||
Inoltre per impedire che l'utente possa guardare il retro dello schermo è consigliabile rendere quest'ultimo solidale con l'utente.
|
Inoltre per impedire che l'utente possa guardare il retro dello schermo è consigliabile rendere quest'ultimo solidale con l'utente.
|
||||||
|
|
||||||
Risultano evidenti non solo le limitazioni, ma anche la natura più di *pezza* che soluzione al problema.
|
Non solo risultano evidenti le limitazioni di questo metodo, ma anche la natura più di *pezza* che soluzione al problema.
|
||||||
|
|
||||||
[^kiss]: Acronimo di *Keep It Simple, Stupid*, è un principio di programmazione che consiglia di concentrarsi su un problema alla volta.
|
[^kiss]: Acronimo di *Keep It Simple, Stupid*, è un principio di programmazione che consiglia di concentrarsi su un problema alla volta.
|
||||||
|
|
||||||
[^chroma-key]: Conosciuto anche con il nome di *green screen*, è una tecnica cinematografica che permette di sovrapporre due sorgenti video. Nel caso specifico la prima fonte video è catturata in real time dal sensore fotografico del device, mentre la secondo è quella che vogliamo integrare nell'ambiente.
|
[^chroma-key]: Conosciuto anche con il nome di *green screen*, è una tecnica cinematografica che permette di sovrapporre due sorgenti video. Nel caso specifico la prima fonte video è catturata in real time dal sensore fotografico del device, mentre la seconda è quella che vogliamo integrare nell'ambiente.
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
### Movimento
|
### Movimento
|
||||||
|
|
||||||
Anche in questo caso Sceneform non mette a disposizione un supporto diretto, ma a differenza di quanto visto con le animazioni, è possibile sopperire a questa mancanza abbastanza facilmente e con risultati soddisfacenti tramite gli `ObjectAnimator`.
|
Anche in questo caso Sceneform non ci fornisce un supporto diretto, ma a differenza di quanto visto con le animazioni, è possibile sopperire a questa mancanza abbastanza facilmente e con risultati soddisfacenti tramite gli `ObjectAnimator`.
|
||||||
|
|
||||||
L'`ObjectAnimator` non è una classe specifica di ARCore o Sceneform, ma dell'SDK di Android che può essere usata per gestire facilmente animazioni e transizioni nelle applicazioni.
|
L'`ObjectAnimator` non è una classe specifica di ARCore o Sceneform, ma dell'SDK di Android che può essere usata per gestire facilmente animazioni e transizioni all'interno delle applicazioni Android.
|
||||||
Inoltre per poter simulare il movimento avremo bisogno di settare dei punti nello spazio e *collegarli* tramite un interpolatore.
|
Grazie a questa classe e una serie di punti nello spazio, *collegati* tramite un interpolatore, saremo in grado di conferire il movimento ai nostri modelli.
|
||||||
|
|
||||||
Per mostrare l'applicazione degli animator è stato realizzato un progetto d'esempio che in seguito al tocco dell'utente renderizzerà un modello del sistema solare in cui i pianeti realizzano sia il modo di rotazione, sia quello di rivoluzione.
|
Per mostrare il funzionamento degli animator è stato realizzato un progetto d'esempio in grado di renderizzare un modello del sistema solare in cui i pianeti realizzano sia il modo di rotazione su se stessi, sia quello di rivoluzione intorno al sole.
|
||||||
|
|
||||||
#### Recupero e rendering dei modelli
|
#### Recupero e rendering dei modelli
|
||||||
|
|
||||||
Visto l'elevato numero di modelli con cui si deve operare si è scelto di recuperarli a runtime.
|
Visto l'elevato numero di modelli con cui si deve operare si è scelto di recuperarli da un server a runtime.
|
||||||
Il procedimento è grossomodo identico a quello visto precedentemente, ma in questo caso si è scelto di evitare l'uso delle callback, al fine di evitare il *callback hell*[^callback-hell], a favore delle *coroutines*, uno strumento messo a disposizione dal linguaggio Kotlin che permette di gestire codice asincrono come se fosse sequenziale.
|
Il procedimento è simile a quello visto precedentemente, con la differenza che in questo caso si è scelto di non usare le callback, al fine di evitare il *callback hell*[^callback-hell], a favore delle *coroutines*, uno strumento messo a disposizione dal linguaggio Kotlin che permette di gestire codice asincrono come se fosse sequenziale.
|
||||||
Inoltre sempre attraverso le *coroutines* è stato possibile eseguire più rendering in parallelo e quindi velocizzare l'applicazione.
|
Inoltre sempre attraverso le *coroutines* è stato possibile eseguire più rendering in parallelo e quindi ottimizzare il tempo di CPU dell'applicazione.
|
||||||
|
|
||||||
All'interno del metodo `onCreate` viene avviata una coroutine che richiama la funzione `loadPlanets`.
|
All'interno del metodo `onCreate` viene avviata una coroutine che richiama la funzione `loadPlanets`.
|
||||||
Inoltre viene conservato un riferimento al `Job` della coroutine.
|
Inoltre viene conservato un riferimento al `Job` della coroutine.
|
||||||
@ -19,16 +19,19 @@ Inoltre viene conservato un riferimento al `Job` della coroutine.
|
|||||||
```kotlin
|
```kotlin
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
// ...
|
// ...
|
||||||
loadPlanetsJob = GlobalScope.launch(Dispatchers.Main) {
|
loadPlanetsJob = GlobalScope.launch(Dispatchers.Main){
|
||||||
renderablePlanets = loadPlanets(this@MainActivity)
|
renderablePlanets = loadPlanets(this@MainActivity)
|
||||||
}
|
}
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
La funzione `loadPlanets` si occupa di caricare e restituire con una `Map` tutti i pianeti del sistema solare.
|
La funzione `loadPlanets` si occupa di caricare e restituire tramite una `Map` tutti i pianeti del sistema solare.
|
||||||
|
Mentre il caricamento del singolo pianeta avviene mediante la funzione `loadPlanet`.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
suspend fun loadPlanets(context: Context): Map<Planet, ModelRenderable> {
|
suspend fun loadPlanets(
|
||||||
|
context: Contex
|
||||||
|
) : Map<Planet, ModelRenderable> {
|
||||||
val sun = loadPlanet(context, Planet.SUN)
|
val sun = loadPlanet(context, Planet.SUN)
|
||||||
/**
|
/**
|
||||||
* ...
|
* ...
|
||||||
@ -45,14 +48,20 @@ suspend fun loadPlanets(context: Context): Map<Planet, ModelRenderable> {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Il caricamento del singolo pianeta avviene mediante la funzione `loadPlanet`.
|
Nella funzione `loadPlanet` viene da prima recuperato il modello tridimensionale dal server e successivamente se ne effettua il rendering attraverso la funzione `buildFutureRenderable`.
|
||||||
Nella funzione da prima viene recuperato il modello tridimensionale dal server e successivamente se ne effettua il rendering attraverso la funzione `buildFutureRenderable`.
|
Quest'ultima, come abbiamo già visto, restituisce un `CompletableFuture` che per poter essere utilizzarlo tramite delle coroutines deve essere trasformarlo in un `Deferred`[^deferred].
|
||||||
Quest'ultima, come abbiamo già visto, restituisce un future che in questo caso viene ottenuto in modo asincrono attraverso il costruttore di coroutines `async`.
|
Questa operazione avviene attraverso il costruttore di coroutines `async`.
|
||||||
Com'è possibile notare viene usato il dispatcher `IO` che ci consente di eseguire l'operazione in background.
|
Inoltre viene usato il dispatcher `IO` che ci consente di eseguire l'operazione in background.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
fun loadPlanet(context: Context, planet: Planet): Deferred<ModelRenderable> {
|
fun loadPlanet(
|
||||||
val modelSource = fetchModel(context, Uri.parse(planet.value))
|
context: Context,
|
||||||
|
planet: Planet
|
||||||
|
): Deferred<ModelRenderable> {
|
||||||
|
val modelSource = fetchModel(
|
||||||
|
context,
|
||||||
|
Uri.parse(planet.value)
|
||||||
|
)
|
||||||
val futureRenderable = buildFutureRenderable(
|
val futureRenderable = buildFutureRenderable(
|
||||||
context,
|
context,
|
||||||
modelSource,
|
modelSource,
|
||||||
@ -65,8 +74,8 @@ fun loadPlanet(context: Context, planet: Planet): Deferred<ModelRenderable> {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
L'ultima operazione da dover fare prima di poter usare i modelli renderizzati è assicurarci che l'operazione di rendering sia stata completata.
|
L'ultima operazione da dover effettuare prima di poter usare i modelli renderizzati è assicurarci che l'operazione di rendering sia stata completata.
|
||||||
Per fare ciò viene usato ancora una volta il costruttore `launch` e si attende finché il job di rendering non sia concluso.
|
Per fare ciò viene usato ancora una volta il costruttore di coroutines `launch` e si attende, in modo non bloccante, il completamento del job di rendering.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
GlobalScope.launch(Dispatchers.Main) {
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
@ -77,37 +86,35 @@ GlobalScope.launch(Dispatchers.Main) {
|
|||||||
|
|
||||||
#### Orbite e pianeti
|
#### Orbite e pianeti
|
||||||
|
|
||||||
Per realizzare le orbite e i pianeti è stata creata la classe `RotationNode` che va ad estendere la classe di libreria `Node`.
|
Per realizzare le orbite e i pianeti è stata implementata la classe `RotationNode` che va ad estendere la classe di libreria `Node`.
|
||||||
|
|
||||||
Componente principale di questa classe è la funzione `createAnimator` che si occupo della creazione dell'`ObjectAnimator` che permette di muovere i modelli.
|
Componente principale di questa è la funzione `createAnimator` che si occupa della creazione dell'`ObjectAnimator` che permette di muovere i modelli.
|
||||||
All'interno della funzione vengono definiti i punti da cui ottenere la rotazione attraverso l'interpolatore.
|
All'interno della funzione vengono definiti i punti da cui ottenere la rotazione attraverso l'interpolatore.
|
||||||
Inoltre viene impostato l'`ObjectAnimator` affinché riproduca in loop l'animazione.
|
Inoltre viene impostato l'`ObjectAnimator` affinché riproduca in loop l'animazione.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
private fun createAnimator(): ObjectAnimator {
|
private fun createAnimator(): ObjectAnimator {
|
||||||
val orientations = arrayOf(0f, 120f, 240f, 360f)
|
val orientations = arrayOf(0f, 120f, 240f, 360f)
|
||||||
.map {
|
.map {
|
||||||
Quaternion
|
Quaternion
|
||||||
.axisAngle(Vector3(0.0f, 1.0f, 0.0f), it)
|
.axisAngle(Vector3(0.0f, 1.0f, 0.0f), it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val orbitAnimation = ObjectAnimator()
|
val orbitAnimation = ObjectAnimator()
|
||||||
orbitAnimation.setObjectValues(*orientations.toTypedArray())
|
orbitAnimation.setObjectValues(*orientations.toTypedArray())
|
||||||
|
|
||||||
orbitAnimation.propertyName = "localRotation"
|
|
||||||
|
|
||||||
orbitAnimation.setEvaluator(QuaternionEvaluator())
|
orbitAnimation.setEvaluator(QuaternionEvaluator())
|
||||||
|
|
||||||
orbitAnimation.repeatCount = ObjectAnimator.INFINITE
|
orbitAnimation.repeatCount = ObjectAnimator.INFINITE
|
||||||
orbitAnimation.repeatMode = ObjectAnimator.RESTART
|
orbitAnimation.repeatMode = ObjectAnimator.RESTART
|
||||||
orbitAnimation.interpolator = LinearInterpolator()
|
orbitAnimation.interpolator = LinearInterpolator()
|
||||||
orbitAnimation.setAutoCancel(true)
|
orbitAnimation.setAutoCancel(true)
|
||||||
|
|
||||||
return orbitAnimation
|
return orbitAnimation
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Sempre nella classe `RotationNode` vanno sovrascritti i metodi `OnActivate` e `OnDeactivate`, per gestire lo start e lo stop dell'animazione.
|
Inoltre nella classe `RotationNode` vanno sovrascritti i metodi `OnActivate` e `OnDeactivate`, per gestire lo start e lo stop dell'animazione.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
override fun onActivate() {
|
override fun onActivate() {
|
||||||
@ -119,8 +126,8 @@ override fun onDeactivate() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Inoltre per la creazione dei pianeti si è reso necessario creare un ulteriore classe, `PlanetNode`, anch'essa estensione della classe `Node`.
|
La creazione dei pianeti è gestita attraverso un ulteriore classe, `PlanetNode`, anch'essa estensione della classe `Node`.
|
||||||
Questa classe altro non è che un nodo con all'interno un `RotationNode`.
|
Questa classe altro non è che un nodo che come attributo ha un `RotationNode`.
|
||||||
|
|
||||||
La creazione delle orbite e dei pianeti avviene mediante la funzione `createPlanetNode`.
|
La creazione delle orbite e dei pianeti avviene mediante la funzione `createPlanetNode`.
|
||||||
L'orbita del pianeta viene ancorata al nodo principale, nel caso specifico il sole, e il pianeta viene ancorato alla sua orbita.
|
L'orbita del pianeta viene ancorata al nodo principale, nel caso specifico il sole, e il pianeta viene ancorato alla sua orbita.
|
||||||
@ -141,7 +148,11 @@ private fun createPlanetNode(
|
|||||||
val renderable = renderablePlanets[planet] ?: return
|
val renderable = renderablePlanets[planet] ?: return
|
||||||
val planetNode = PlanetNode(renderable)
|
val planetNode = PlanetNode(renderable)
|
||||||
planetNode.setParent(orbit)
|
planetNode.setParent(orbit)
|
||||||
planetNode.localPosition = Vector3(AU_TO_METERS * auFromParent, 0.0f, 0.0f)
|
planetNode.localPosition = Vector3(
|
||||||
|
AU_TO_METERS * auFromParent,
|
||||||
|
0.0f,
|
||||||
|
0.0f
|
||||||
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -149,10 +160,12 @@ private fun createPlanetNode(
|
|||||||
|
|
||||||
#### Creazione e aggiunta del sistema solare alla scena
|
#### Creazione e aggiunta del sistema solare alla scena
|
||||||
|
|
||||||
La creazione del sistema solare avviene mediante la funzione `createSolarSystem` che riceve in ingresso la `Map` con tutti i modelli dei pianeti e li posizionare intorno al sole restituendo infine quest'ultimo.
|
La creazione del nostro sistema solare avviene mediante la funzione `createSolarSystem` che riceve in ingresso la `Map` con tutti i modelli dei pianeti, li posizionare intorno al sole e infine restituisce quest'ultimo.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
private fun createSolarSystem(renderablePlanets: Map<Planet, ModelRenderable>): Node {
|
private fun createSolarSystem(
|
||||||
|
renderablePlanets: Map<Planet, ModelRenderable>
|
||||||
|
): Node {
|
||||||
val base = Node()
|
val base = Node()
|
||||||
|
|
||||||
val sun = Node()
|
val sun = Node()
|
||||||
@ -164,22 +177,40 @@ private fun createSolarSystem(renderablePlanets: Map<Planet, ModelRenderable>):
|
|||||||
sunVisual.renderable = renderablePlanets[Planet.SUN]
|
sunVisual.renderable = renderablePlanets[Planet.SUN]
|
||||||
sunVisual.localScale = Vector3(0.5f, 0.5f, 0.5f)
|
sunVisual.localScale = Vector3(0.5f, 0.5f, 0.5f)
|
||||||
|
|
||||||
createPlanetNode(Planet.MERCURY, sun, 0.4f, 47f, renderablePlanets)
|
createPlanetNode(
|
||||||
|
Planet.MERCURY,
|
||||||
|
sun,
|
||||||
|
0.4f,
|
||||||
|
47f,
|
||||||
|
renderablePlanets
|
||||||
|
)
|
||||||
// ...
|
// ...
|
||||||
createPlanetNode(Planet.NEPTUNE, sun, 6.1f, 5f, renderablePlanets)
|
createPlanetNode(
|
||||||
|
Planet.NEPTUNE,
|
||||||
|
sun,
|
||||||
|
6.1f,
|
||||||
|
5f,
|
||||||
|
renderablePlanets
|
||||||
|
)
|
||||||
|
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
L'aggiunta alla scene avviene mediante la ben nota funzione `addNodeToScene`.
|
L'aggiunta dei modelli alla scene avviene mediante la ben nota funzione `addNodeToScene`.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
val solarSystem = createSolarSystem(renderablePlanets)
|
val solarSystem = createSolarSystem(renderablePlanets)
|
||||||
addNodeToScene(arFragment, hitResult.createAnchor(), solarSystem)
|
addNodeToScene(
|
||||||
|
arFragment,
|
||||||
|
hitResult.createAnchor(),
|
||||||
|
solarSystem
|
||||||
|
)
|
||||||
isModelAdded = true
|
isModelAdded = true
|
||||||
```
|
```
|
||||||
|
|
||||||
Anche in questo caso si rende necessario l'utilizzo di un flag booleano per evitare l'aggiunta di più sistemi solari.
|
Anche in questo caso si rende necessario l'utilizzo di un flag booleano per evitare l'aggiunta di più sistemi solari.
|
||||||
|
|
||||||
[^callback-hell]: Con il termine *callback hell* si indica l'utilizzo eccessivo di callback all'interno di altre callback. Questo fenomeno comporta una diminuzione della leggibilità del codice e un aumento della complessità e di conseguenza della presenza di bug.
|
[^callback-hell]: Con il termine *callback hell* si indica l'utilizzo eccessivo di callback all'interno di altre callback. Questo fenomeno comporta una diminuzione della leggibilità del codice e un aumento della complessità e di conseguenza della presenza di bug.
|
||||||
|
|
||||||
|
[^deferred]: In Kotlin i *future* sono gestiti mediante l'oggetto `Deferred<T>`.
|
||||||
|
Loading…
Reference in New Issue
Block a user