Codifica un'app di misurazione con ARKit interazione e misurazione

Insieme a molte altre cose che sono state rapidamente sostituite dalla nostra tecnologia moderna, sembra che il metro a nastro comune possa essere il prossimo ad andare. In questa serie di tutorial in due parti, stiamo imparando come utilizzare la realtà aumentata e la fotocamera sul tuo dispositivo iOS per creare un'app che riporti la distanza tra due punti.

Nel primo post abbiamo creato il progetto dell'app e abbiamo codificato i suoi elementi di interfaccia principali. In questo post, lo completeremo misurando tra due punti nella scena AR. Se non lo hai ancora fatto, segui il primo post per ottenere il tuo progetto ARKit.

Rubinetti

Ecco una delle parti più importanti di questo tutorial: la gestione quando l'utente tocca il loro mondo per far apparire una sfera esattamente dove ha toccato. Più tardi, calcoleremo la distanza tra queste sfere per mostrare finalmente all'utente la loro distanza.

Tocca Gesture Recognizer

Il primo passaggio nel controllo dei tocchi consiste nel creare un riconoscitore del gesto di tocco all'avvio dell'app. Per fare questo, crea un gestore di tap come segue:

// Crea un gestore tap e quindi lo imposta su una costante, lasciando tapRecognizer = UITapGestureRecognizer (target: self, action: #selector (handleTap))

La prima riga crea un'istanza di UITapGestureRecognizer () classe e passa in due parametri all'inizializzazione: il bersaglio e l'azione. L'obiettivo è il destinatario delle notifiche inviate da questo strumento di riconoscimento e noi le vogliamo ViewController classe per essere l'obiettivo. L'azione è semplicemente un metodo che dovrebbe essere chiamato ogni volta che c'è un tocco.

Per impostare il numero di tocchi, aggiungere questo:

// Imposta la quantità di tap necessari per attivare il gestore tapRecognizer.numberOfTapsRequired = 1

Successivamente, l'istanza della classe che abbiamo creato in precedenza deve sapere quanti tap sono effettivamente necessari per attivare il riconoscimento. Nel nostro caso, abbiamo solo bisogno di un tocco, ma in altre app, potrebbe essere necessario avere più (ad esempio un doppio tocco) per alcuni casi.

Aggiungi il gestore alla vista scena in questo modo:

// Aggiunge il gestore alla vista scena sceneView.addGestureRecognizer (tapRecognizer)

Infine, questa singola riga di codice aggiunge semplicemente il riconoscitore di gesti al sceneView, che è dove faremo tutto. Qui è dove sarà l'anteprima della fotocamera e ciò che l'utente direttamente toccherà per far apparire una sfera sullo schermo, quindi ha senso aggiungere il riconoscitore alla vista con cui l'utente interagirà.

Handle Tap Method

Quando abbiamo creato il UITapGestureRecognizer (), potresti ricordare che abbiamo impostato a handleTap metodo per l'azione. Ora, siamo pronti a dichiarare questo metodo. Per fare ciò, aggiungi semplicemente quanto segue alla tua app:

@objc func handleTap (mittente: UITapGestureRecognizer) // Il tuo codice va qui 

Sebbene la dichiarazione di funzione possa essere abbastanza auto-esplicativa, ci si potrebbe chiedere perché ci sia un @objc taggagli di fronte. A partire dalla versione corrente di Swift, per esporre i metodi a Objective-C, è necessario questo tag. Tutto ciò che devi sapere è questo #selettore ha bisogno che il metodo indicato sia disponibile per l'obiettivo-C. Infine, il parametro method ci permetterà di ottenere la posizione esatta che è stata toccata sullo schermo.

Rilevamento posizione

Il prossimo passo per far apparire le nostre sfere dove l'utente ha toccato è rilevare la posizione esatta che hanno sfruttato. Ora, questo non è semplice come ottenere la posizione e posizionare una sfera, ma sono sicuro che lo padronerai in pochissimo tempo. 

Inizia aggiungendo le seguenti tre righe di codice al tuo handleTap () metodo:

// Ottiene la posizione del rubinetto e lo assegna a una costante let location = sender.location (in: sceneView) // Cerca oggetti reali come superfici e filtri su superfici piane lascia hitTest = sceneView.hitTest (posizione, tipi : [ARHitTestResult.ResultType.featurePoint]) // Assegna il risultato più accurato a una costante se è non-nil guardia lascia risultato = hitTest.last else return

Se ricordi il parametro che abbiamo preso nel handleTap () metodo, è possibile ricordare che è stato nominato mittente, ed era di tipo UITapGestureRecognizer. Bene, questa prima riga di codice prende semplicemente la posizione del rubinetto sullo schermo (relativamente alla vista scena) e la imposta su una costante chiamata Posizione.

Successivamente, stiamo facendo qualcosa chiamato hit test sul SceneView si. In termini semplici, ciò che fa è controllare la scena per oggetti reali, come tavoli, superfici, muri, pavimenti, ecc. Questo ci consente di ottenere un senso di profondità e di ottenere misurazioni accurate tra due punti. Inoltre, stiamo specificando i tipi di oggetti da rilevare e, come puoi vedere, gli stiamo dicendo di cercare featurePoints, che sono essenzialmente superfici piatte, il che ha senso per un'app di misurazione.

Infine, la riga di codice prende il risultato più accurato, che nel caso di hitTest è l'ultimo risultato e controlla se non lo è zero. Se lo è, ignora il resto delle righe in questo metodo, ma se c'è davvero un risultato, verrà assegnato ad una costante chiamata risultato.

matrici

Se ripensi alla tua lezione di algebra del liceo, potresti ricordare le matrici, che all'epoca non erano così importanti come allora. Sono comunemente usati nelle attività relative alla grafica del computer e li vedremo in questa app.

Aggiungi le seguenti linee al tuo handleTap () metodo, e li esamineremo in dettaglio:

// Converte matrix_float4x4 in una SCNMatrix4 da utilizzare con SceneKit let transform = SCNMatrix4.init (result.worldTransform) // Crea un SCNVector3 con alcuni indici nella matrice let vector = SCNVector3Make (transform.m41, transform.m42, transform. m43) // Crea una nuova sfera con il metodo creato let sphere = newSphere (at: vector)

Prima di entrare nella prima riga di codice, è importante capire che l'hit test che abbiamo fatto prima restituisce un tipo di matrix_float4x4, che è essenzialmente una matrice quattro per quattro di valori float. Dal momento che siamo dentroSceneKit, però, avremo bisogno di convertirlo in qualcosa che SceneKit può capire - in questo caso, in a SCNMatrix4.

Quindi, useremo questa matrice per creare un SCNVector3, che, come suggerisce il nome, è un vettore con tre componenti. Come avrai intuito, quei componenti sono Xy, e z, per darci una posizione nello spazio. transform.m41transform.m42, e transform.m43 sono i valori delle coordinate rilevanti per i vettori a tre componenti.

Infine, usiamo il newsphere () metodo che abbiamo creato in precedenza, insieme alle informazioni sulla posizione che abbiamo analizzato dall'evento touch, per creare una sfera e assegnarla a una costante chiamata sfera.

Risolvere il problema del doppio tocco

Ora, potresti aver realizzato un leggero difetto nel nostro codice; se l'utente continua a toccare, una nuova sfera continua a essere creata. Non vogliamo questo perché rende difficile determinare quali sfere debbano essere misurate. Inoltre, è difficile per l'utente tenere traccia di tutte le sfere!

Risoluzione con array

Il primo passo per risolverlo è creare una matrice nella parte superiore della classe.

var sphere: [SCNNode] = []

Questa è una matrice di SCNNodes perché è il tipo da cui siamo tornati newsphere () metodo che abbiamo creato all'inizio di questo tutorial. In seguito, metteremo le sfere in questo array e controlleremo quanti ce ne sono. Sulla base di ciò, saremo in grado di manipolare i loro numeri rimuovendoli e aggiungendoli.

Associazione facoltativa

Successivamente, useremo una serie di istruzioni if-else e per i loop per capire se ci sono delle sfere nell'array o no. Per iniziare, aggiungi il seguente collegamento facoltativo alla tua app:

se preferisci first = spheres.first // Il tuo codice va qui else // Il tuo codice va qui

Per prima cosa, controlliamo se ci sono qualunque articoli in sfere array, e se no, esegui il codice nel file altro clausola.

Auditing delle sfere

Successivamente, aggiungi quanto segue alla prima parte (il Se ramo) del tuo if-elsedichiarazione:

// Aggiunge una seconda sfera all'array spheres.append (sfera) print (sphere.distance (a: first)) // Se ne sono presenti più di due ... if spheres.count> 2 // Iterate attraverso l'array di sfere per la sfera in sfere // Rimuovi tutte le sfere sphere.removeFromParentNode () // Rimuovi sfere estranee sfere = [sfere [2]] 

Dato che siamo già in un evento tap, sappiamo che stiamo creando un'altra sfera. Quindi, se esiste già una sfera, dobbiamo ottenere la distanza e mostrarla all'utente. Puoi chiamare il distanza() metodo sulla sfera, perché in seguito creeremo un'estensione di SCNNode.

Successivamente, abbiamo bisogno di sapere se ci sono già più del massimo di due sfere. Per fare questo, stiamo usando solo la proprietà count del nostro sfere array e an Se dichiarazione. Effettuiamo l'iterazione attraverso tutte le sfere dell'array e le rimuoviamo dalla scena. (Non ti preoccupare, ne riparliamo più tardi).

Finalmente, visto che siamo già nel Se dichiarazione che ci dice che ci sono più di due sfere, possiamo rimuovere la terza nell'array in modo che ci assicuriamo che ne restino solo due nell'array in ogni momento.

Aggiungere le sfere

Finalmente, nel altro clausola, sappiamo che il sfere array è vuoto, quindi quello che dobbiamo fare è aggiungere semplicemente la sfera che abbiamo creato al momento della chiamata al metodo. Dentro il tuo altro clausola, aggiungere questo:

// Aggiungi la sfera sferica.append (sfera)

Sìì! Abbiamo appena aggiunto la sfera al nostro sfere array, e il nostro array è pronto per il prossimo tocco. Ora abbiamo preparato il nostro array con le sfere che dovrebbero essere sullo schermo, quindi ora aggiungiamole all'array.

Per iterare e aggiungere le sfere, aggiungi questo codice:

// Iterate attraverso l'array di sfere per sfere in sfere // Aggiungi tutte le sfere dell'array self.sceneView.scene.rootNode.addChildNode (sphere)

Questo è solo un semplice per loop, e stiamo aggiungendo le sfere (SCNNode) Come figlio del nodo radice della scena. In SceneKit, questo è il modo preferito per aggiungere cose.

Metodo completo

Ecco cosa è il finale handleTap () il metodo dovrebbe assomigliare a:

@objc func handleTap (mittente: UITapGestureRecognizer) let location = sender.location (in: sceneView) let hitTest = sceneView.hitTest (posizione, tipi: [ARHitTestResult.ResultType.featurePoint]) guardia let result = hitTest.last else return  let transform = SCNMatrix4.init (result.worldTransform) let vector = SCNVector3Make (transform.m41, transform.m42, transform.m43) lascia sphere = newSphere (a: vector) se preferisci first = spheres.first spheres.append ( sfera) stampa (sphere.distance (to: first)) if spheres.count> 2 for sphere in spheres sphere.removeFromParentNode () spheres = [spheres [2]] else spheres.append (sphere) for sphere in spheres self.sceneView.scene.rootNode.addChildNode (sphere)

Calcolo delle distanze

Ora, se ti ricorderai, abbiamo chiamato a distanza (a :) metodo sul nostro SCNNode, la sfera, e sono sicuro che Xcode ti sta urlando contro per aver usato un metodo non dichiarato. Finiamola ora, creando un'estensione del SCNNode classe.

Per creare un'estensione, fai semplicemente quanto segue al di fuori del tuo ViewController classe:

estensione SCNNode // Il tuo codice va qui

Questo ti consente semplicemente di modificare la classe (è come se stessimo modificando la classe effettiva). Quindi, aggiungeremo un metodo che calcolerà la distanza tra due nodi.

Ecco la dichiarazione della funzione per farlo:

func distance (to destination: SCNNode) -> CGFloat // Il tuo codice va qui

Se vedrai, c'è un parametro che è un altro SCNNode, e restituisce a CGFloat come risultato. Per il calcolo effettivo, aggiungi questo al tuo distanza() funzione:

let dx = destination.position.x - position.x let dy = destination.position.y - position.y let dz = destination.position.z - position.z let inches: Float = 39.3701 let meters = sqrt (dx * dx + dy * dy + dz * dz) return CGFloat (metri * pollici)

Le prime tre righe di codice sottraggono le posizioni x, y e z della corrente SCNNode dalle coordinate del nodo passato come parametro. In seguito inseriremo questi valori nella formula della distanza per ottenere la loro distanza. Inoltre, poiché voglio il risultato in pollici, ho creato una costante per il tasso di conversione tra metri e pollici per una facile conversione in seguito. 

Ora, per ottenere la distanza tra i due nodi, ripensa alla classe di matematica della scuola media: potresti ricordare la formula della distanza per il piano cartesiano. Qui, lo applichiamo ai punti nello spazio tridimensionale.

Infine, restituiamo il valore moltiplicato per il rapporto di conversione in pollici per ottenere l'unità di misura appropriata. Se vivi fuori dagli Stati Uniti, puoi lasciarlo in metri o convertirlo in centimetri se lo desideri.

Conclusione

Bene, questo è un involucro! Ecco come dovrebbe essere il tuo progetto finale:

Come puoi vedere, le misure non sono perfette, ma pensa che un computer da 15 pollici sia di circa 14.998 pollici, quindi non è male!

Ora sai come misurare le distanze usando la nuova libreria di Apple, Arkit. Questa app può essere utilizzata per molte cose, e ti sfido a pensare a modi diversi in cui ciò può essere usato nel mondo reale, e assicurati di lasciare i tuoi pensieri nei commenti qui sotto.

Inoltre, assicurati di controllare il repository GitHub per questa app. E mentre sei ancora qui, dai un'occhiata agli altri tutorial di sviluppo iOS qui su Envato Tuts+!