Kode en måleapp med ARKit Interagere og måle

Sammen med mange andre ting som raskt er blitt erstattet av vår moderne teknologi, ser det ut som om det vanligste målebåndet kan være neste. I denne todelte opplæringsserien lærer vi hvordan du bruker forstørret virkelighet og kameraet på din iOS-enhet for å lage en app som vil rapportere avstanden mellom to punkter.

I det første innlegget opprettet vi app-prosjektet og kodet dets hovedgrensesnittelementer. I dette innlegget vil vi fullføre det ved å måle mellom to punkter i AR-scenen. Hvis du ikke har det ennå, følg med det første innlegget for å få ARKit-prosjektet opprettet.

Håndtering av kraner

Her er en av de største delene av denne opplæringen: Håndtering når brukeren tapper på sin verden for å få en kule til å vises akkurat der de tappet. Senere beregner vi avstanden mellom disse områdene for endelig å vise brukeren deres avstand.

Trykk på Gesture Recognizer

Det første trinnet i å sjekke kraner er å opprette en gjenkjenning for trykkbevegelse når appen starter. For å gjøre dette, opprett en trykkbehandler som følger:

// Oppretter en trykkbehandler og setter den til en konstant la tapRecognizer = UITapGestureRecognizer (mål: selv, handling: #selector (handleTap))

Den første linjen skaper en forekomst av UITapGestureRecognizer () klasse og passerer i to parametere ved initialiseringen: målet og handlingen. Målet er mottakeren av varslene som denne gjenkjenneren sender, og vi vil ha vår ViewController klasse for å være målet. Handlingen er bare en metode som skal kalles hver gang det er trykk.

For å angi antall kraner, legg til dette:

// Angir mengden kraner som trengs for å utløse handleren tapRecognizer.numberOfTapsRequired = 1

Deretter må forekomsten av klassen som vi opprettet tidligere, vite hvor mange kraner det egentlig er nødvendig for å aktivere gjenkjenneren. I vårt tilfelle trenger vi bare ett trykk, men i andre apper kan det hende du må ha mer (for eksempel en dobbeltkran) for noen tilfeller.

Legg til handleren på scenevisningen som denne:

// Legger til handleren til scenevisningen sceneView.addGestureRecognizer (tapRecognizer)

Til slutt legger denne ene linjen med kode bare gjenkjenningsgjenkjenningen til sceneView, som er hvor vi skal gjøre alt. Dette er hvor forhåndsvisningen av kameraet vil være så vel som det som brukeren klikker direkte for å få en sfære til å vises på skjermen, så det er fornuftig å legge til gjenkjenningen til visningen som brukeren vil samhandle med.

Håndtak Trykk metode

Da vi opprettet UITapGestureRecognizer (), Du kan huske at vi satte en handleTap metode til handlingen. Nå er vi klare til å erklære den metoden. For å gjøre dette, må du bare legge til følgende i appen din:

@objc func handleTap (avsender: UITapGestureRecognizer) // Din kode går her 

Selv om funksjonsdeklarasjonen kan være ganske selvforklarende, kan du lure på hvorfor det er en @objc tag foran den. Fra den nåværende versjonen av Swift, for å avsløre metoder til Objective-C, trenger du denne taggen. Alt du trenger å vite er det #selector trenger den refererte metoden til å være tilgjengelig for Objective-C. Til slutt vil metoden parameteren la oss få den nøyaktige plasseringen som ble tappet på skjermen.

Steddeteksjon

Det neste skrittet i å få våre sfærer til å vises hvor brukeren tappede, er å oppdage den nøyaktige posisjonen som de tappede på. Nå er dette ikke så enkelt som å få plassering og plassere en sfære, men jeg er sikker på at du vil mestre det på kort tid. 

Begynn med å legge til følgende tre linjer med kode til din handleTap () metode:

// Gets plasseringen av trykknappen og tildeler den til et konstant la sted = sender.location (i: sceneView) // Søker etter virkelige verdensobjekter som overflater og filtrerer ut flate flater la hitTest = sceneView.hitTest (sted, typer : [ARHitTestResult.ResultType.featurePoint]) // Tilordner det mest nøyaktige resultatet til en konstant hvis den ikke er null beskyttet, la resultatet = hitTest.last annet return

Hvis du husker parameteren vi tok i handleTap () metode, kan du huske at den ble oppkalt avsender, og det var av typen UITapGestureRecognizer. Vel, denne første linje med kode tar bare plasseringen av trykk på skjermen (i forhold til scenevisningen), og setter den til en konstant navngitt plassering.

Deretter gjør vi noe som heter en hittest på SceneView seg selv. Dette gjør det enklere å sjekke scenen for ekte objekter, for eksempel bord, flater, vegger, gulv osv. Dette gjør at vi får en følelse av dybde og å få ganske nøyaktige målinger mellom to punkter. I tillegg spesifiserer vi hvilke objekter du vil oppdage, og som du kan se, forteller vi det å lete etter featurePoints, som er i hovedsak flate flater, noe som gir mening for en måleapp.

Til slutt tar koden med det mest nøyaktige resultatet, som i tilfelle av hitTest er det siste resultatet, og sjekker om det ikke er det nil. Hvis det er, ignorerer det resten av linjene i denne metoden, men hvis det virkelig er et resultat, blir det tildelt en konstant kalt resultat.

matriser

Hvis du tenker tilbake til algebra klassen din, kan du huske matriser, som kanskje ikke virket så viktige igjen da de er akkurat nå. De brukes vanligvis i datagrafikkrelaterte oppgaver, og vi får et glimt av dem i denne appen.

Legg til følgende linjer i din handleTap () metode, og vi vil gå over dem i detalj:

// Konverterer matrix_float4x4 til en SCNMatrix4 som skal brukes med SceneKit la transformere = SCNMatrix4.init (result.worldTransform) // Oppretter en SCNVector3 med visse indekser i matrisen la vektor = SCNVector3Make (transform.m41, transform.m42, transform. m43) // Lag en ny sfære med den opprettede metoden la sfære = newSphere (ved: vektor)

Før du kommer inn i første linje med kode, er det viktig å forstå at hitprøven vi gjorde tidligere returnerer en type matrix_float4x4, som i hovedsak er en fire-fire-fire matrise av flytverdier. Siden vi er inneSceneKit, skjønt, må vi konvertere det til noe som SceneKit kan forstå - i dette tilfellet til en SCNMatrix4.

Da bruker vi denne matrisen til å lage en SCNVector3, som, som navnet antyder, er en vektor med tre komponenter. Som du kanskje har gjettet, er disse komponentene xy, og z, å gi oss en plass i rommet. transform.m41transform.m42, og transform.m43 er de relevante koordinatverdiene for de tre komponentvektorene.

Til slutt, la oss bruke newSphere () metode som vi opprettet tidligere, sammen med posisjonsinformasjonen vi analyserte fra berøringshendelsen, for å lage en sfære og tildele den til en konstant kalt sfære.

Løse Double-Tap-feilen

Nå har du kanskje innsett en liten feil i vår kode; Hvis brukeren fortsetter å tappe, vil en ny sfære fortsette å bli opprettet. Vi ønsker ikke dette fordi det gjør det vanskelig å avgjøre hvilke sfærer som må måles. Det er også vanskelig for brukeren å holde oversikt over alle sfærene!

Løse med arrays

Det første trinnet for å løse dette er å lage en matrise øverst i klassen.

var sfærer: [SCNNode] = []

Dette er en rekke av SCNNodes fordi det er typen vi returnerte fra vår newSphere () metode som vi opprettet til begynnelsen av denne opplæringen. Senere legger vi sfærene i denne gruppen og sjekker hvor mange er det. Basert på det vil vi kunne manipulere tallene deres ved å fjerne og legge til dem.

Valgfri binding

Deretter bruker vi en rekke if-else-setninger og for looper å finne ut om det er noen sfærer i arrayet eller ikke. Til å begynne med, legg til følgende valgfrie binding til appen din:

hvis la først = spheres.first // Din kode går her ellers // Din kode går her

Først sjekker vi om det er noen elementer i kuler array, og hvis ikke, kjør koden i ellers klausul.

Revisjon av sfærene

Deretter legger du til følgende i første del ( hvis gren) av ditt if-elseuttalelse:

// legger til et annet sfære i array spheres.append (sfære) utskrift (sphere.distance (til: først)) // Hvis flere de to er til stede ... hvis spheres.count> 2 // Iterate gjennom sfærer array for sfære i sfærer // Fjern alle sfærer sphere.removeFromParentNode () // Fjern fremmede sfærer spheres = [sfærer [2]] 

Siden vi allerede er i et trykkhendelse, vet vi at vi lager en annen sfære. Så hvis det allerede er en sfære, må vi få avstanden og vise den til brukeren. Du kan ringe til avstand() metode på sfæren, fordi senere lager vi en utvidelse av SCNNode.

Deretter må vi vite om det allerede er mer enn det maksimale av to sfærer. For å gjøre dette bruker vi bare telleiendommen til vår kuler array og an hvis uttalelse. Vi itererer gjennom alle sfærene i arrayet og fjerner dem fra scenen. (Ikke vær redd, vi kommer tilbake til noen av dem senere.)

Til slutt, siden vi allerede er i hvis setning som forteller oss at det er mer enn to sfærer, kan vi fjerne den tredje i gruppen slik at vi sikrer at kun to er igjen i arrayet til enhver tid.

Legge til sfærene

Til slutt, i ellers klausul, vi vet at kuler array er tomt, så det vi trenger å gjøre er å bare legge til sfæren som vi opprettet på tidspunktet for metallsamtalen. Inne i din ellers klausul, legg til dette:

// Legg til sfæren spheres.append (sfæren)

Jippi! Vi har nettopp lagt til sfæren til vår kuler array, og vårt utvalg er klar for neste trykk. Vi har nå utarbeidet vårt utvalg med sfærene som skal være på skjermen, så nå, la oss bare legge til disse i matrisen.

For å iterere gjennom og legge til kulene, legg til denne koden:

// Iterere gjennom sfærer for sfære i sfærer // Legg alle sfærer i arrayet selv.sceneView.scene.rootNode.addChildNode (sfære)

Dette er bare en enkel til loop, og vi legger til kulene (SCNNode) Som et barn av scenens rotknutepunkt. I SceneKit er dette den foretrukne måten å legge til ting.

Full metode

Her er det siste handleTap () metoden skal se ut som:

@objc func handleTap (sender: UITapGestureRecognizer) la location = sender.location (i: sceneView) la hitTest = sceneView.hitTest (plassering, typer: [ARHitTestResult.ResultType.featurePoint]) vakt la resultatet = hitTest.last annet  la transform = SCNMatrix4.init (result.worldTransform) la vektor = SCNVector3Make (transform.m41, transform.m42, transform.m43) la sfære = newSphere (ved: vektor) hvis la først = spheres.first spheres.append ( sfære) (sphere.distance (til: først)) hvis spheres.count> 2 for sfæren i sfærer sphere.removeFromParentNode () spheres = [sfærer [2]] else spheres.append for sfære i sfærer self.sceneView.scene.rootNode.addChildNode (sfære)

Beregne avstander

Nå, hvis du husker, ringte vi en avstand (for å :) metode på vår SCNNode, sfæren, og jeg er sikker på at Xcode roper på deg for å bruke en svart metode. La oss avslutte det nå, ved å lage en utvidelse av SCNNode klasse.

For å opprette en utvidelse, gjør du bare følgende utenfor din ViewController klasse:

utvidelse SCNNode // Din kode går her

Dette lar deg bare endre klassen (det er som om du redigerer den faktiske klassen). Deretter legger vi til en metode som beregner avstanden mellom to noder.

Her er funksjonserklæringen for å gjøre det:

func avstand (til destinasjon: SCNNode) -> CGFloat // Din kode går her

Hvis du vil se, er det en parameter som er en annen SCNNode, og det returnerer a CGFloat som resultatet. For den faktiske beregningen, legg dette til din avstand() funksjon:

la dx = destination.position.x - position.x la dy = destination.position.y - position.y la dz = destination.position.z - position.z la inches: Float = 39.3701 la meter = sqrt (dx * dx + dy * dy + dz * dz) returnerer CGFloat (meter * tommer)

De tre første kodelinjene trekker gjeldende x-, y- og z-posisjoner SCNNode fra koordinatene til noden bestått som en parameter. Vi kobler senere disse verdiene til avstandsformelen for å få avstanden deres. Også fordi jeg vil ha resultatet i inches, har jeg skapt en konstant for konverteringsfrekvensen mellom meter og tommer for enkel konvertering senere. 

Nå, for å få avstanden mellom de to knutepunktene, tenk tilbake til middelskole-matematikklassen din: du kan huske avstandsformelen for det kartesiske flyet. Her bruker vi den til å peke i tredimensjonalt rom.

Til slutt returnerer vi verdien multiplisert med inches-konverteringsforholdet for å få den aktuelle måleenheten. Hvis du bor utenfor USA, kan du legge den i meter eller konvertere den til centimeter hvis du ønsker det.

Konklusjon

Vel, det er en vikle! Her er hva ditt siste prosjekt skal se ut:

Som du kan se, er målingene ikke perfekte, men det synes en 15-tommers datamaskin er rundt 14998 tommer, så det er ikke dårlig!

Nå vet du hvordan du måler avstander ved hjelp av Apples nye bibliotek, ARKit. Denne appen kan brukes til mange ting, og jeg utfordrer deg til å tenke på forskjellige måter som dette kan brukes i den virkelige verden, og sørg for å legge igjen tankene dine i kommentarene nedenfor.

Også, sørg for å sjekke ut GitHub repo for denne appen. Og mens du fortsatt er her, sjekk ut våre andre iOS-utviklingsopplæringer her på Envato Tuts+!