Neem contact op

Hoe we een robot hebben leren rijden

Met enkel React Native en een paar vingers

February 29, 2020
door Dylan Jongbloed  
Cover image

Humanoids heeft een nieuwe installatie tool voor een agrarische robot ontwikkeld. Om het programmeren van de robot 'menselijker' te maken, ontwierpen onze eigen UX designers een app waarin routes getekend kunnen worden door middel van ‘touch-gestures’ op het scherm van een Android of iOS tablet. Graag nemen we je mee en vertellen je hoe ons development team dit design heeft omgezet naar een realiteit.

De technische kern van touch-based tekenen

Omdat we support voor zowel Android tablets als iPads wilden garanderen viel onze ontwikkelplatform keuze al snel op React Native. Echter, het tekenen van routes door middel van touch gebaren op tablets is geen veel voorkomende eigenschap van apps, en zeker geen stock-feature van React Native. Dit betekende dat ons development team creatief moest omgaan met het combineren van bestaande Node modules en React Native functies om de gewenste functionaliteiten te realiseren.

‘De menselijke touch’ integreren

React Native wordt geleverd met de zogeheten ‘PanResponder’ - een functie die het mogelijk maakt om verschillende soorten single- en multi-touch gebaren op het tablet scherm te herkennen en zo te koppelen aan verschillende visuals op het tablet scherm.

Vanzelfsprekend moesten touch-gebaren van de eindgebruiker een visuele weerslag krijgen - in ons geval visualiseerden we een nieuw getekende route door middel van lijnen en cirkels van verschillende afmetingen.

React Native SVG: Tast omzetten in zicht

Voor de visuele weerslag van touch-gebaren gebruikten we React Native SVG. Dit is een module die het mogelijk maakt om in React Native, op dynamische wijze selecteerbare SVGs te genereren en te plaatsen door middel van gespecificeerde coördinaten. En niet zomaar coördinaten - deze coördinaten genereren wij door gebruikers-interactie met het tablet scherm. Kort door de bocht: een horizontaal touch gebaar van een enkele vinger die 20 pixels van links naar rechts bewoog, gaf aan React Native SVG door dat deze een lijnstuk moest genereren van 20 pixels breed, beginnende bij de eerste aanraking op het scherm, en eindigend op de plek waar de vinger het scherm los liet. Voor meer over SVGs, lees onze blog over SVGs in combinatie met React Native.

De 3 primaire touch ‘states’

Om accuraat te visualiseren welke lijnen reeds getekend zijn, was het van belang om route data op te slaan in zogeheten ‘states’. Dit zijn variabelen die data bevatten en geupdate kunnen worden in React Native en zodoende visuele veranderingen teweeg te brengen in het scherm. In het geval van route tekenen ging dit om drie primaire ‘states’, welke elk coördinaten bevatten die van belang zijn voor het visualiseren van route lijnen. Deze coördinaten definieerden wij in een Cartesisch stelsel, bestaande uit een X- en Y-as. Als voorbeeld: een lijn wordt in dit stelsel gedefinieerd door twee punten, elk bestaande uit een X- en Y-coördinaat, welke verbonden zijn door een rechte lijn.

Een voorbeeld van een wiskundige definitie van een lijnstuk

Nu het duidelijk is hoe we lijnstukken definiëren in React Native, gaan we nu verder met de verschillende manieren waarop we deze lijnstukken optellen en bewaren in verschillende states. De 3 primaire states bestaan uit:

1. lines

Deze state bevat de coördinaten die React Native SVG uitleest om SVGs van reeds getekende lijnen te genereren.

[
  {
    "x1": "number", "y1": "number", "x2": "number", "y2": "number"
  },
  {
    "x1": "number", "y1": "number", "x2": "number", "y2": "number"  }
7]

2. lineStart & lineEnd

Deze twee states worden gevuld door touch-informatie die wordt gegenereerd wanneer de gebruiker tijdens het tekenen het scherm aanraakt (lineStart) op een bepaald coördinaat, en wanneer de gebruiker het scherm loslaat (lineEnd).

{
  "x": "number", 
  "y": "number" 
4}

3. projectedLine

De lineStart en lineEnd states komen samen in deze ‘projectedLine’ state. Deze state visualiseert de meest actuele lijn welke getekend wordt wanneer de gebruiker over het teken canvas veegt.

{
  "x1": "number", 
  "y1": "number", 
  "x2": "number", 
  "y2": "number" 
6}


React Componenten voor muren en lijnen

Nu beschreven is hoe route-lijnen worden gevisualiseerd en hoe touch-interactie hiermee wordt gecombineerd is het tijd om te kijken naar hoe deze elementen worden gecombineerd in React componenten en het tekenscherm. Ons tekenscherm bevat drie primaire React componenten:

1. Het 'muur' component

Om routes te tekenen op een bestaande plattegrond, is het van belang om muren te visualiseren. Om een plattegrond van een bestaande ruimte te visualiseren maken we gebruik van GeoJSON: een gestandaardiseerd format voor het vastleggen van geografische data in het JSON bestandstype. Dit format bevat een array welke een verzameling van lijnsegment-objecten bevat. Door middel van de ‘.map’ Array methode, genereren we via React Native SVG een lijn-SVG voor ieder muursegment. In z’n geheel resulteert dit in 1 component dat alle muren visualiseert en lokaliseert op exacte geografische coördinaten, en op schaal - ideaal!

2. Het 'projection line' component

Dit component wordt tijdelijk in licht paars zichtbaar wanneer het scherm een teken-touch-gebaar herkent.

Door middel de ‘useEffect’ hook van React Native, wordt deze projectielijn geupdate wanneer de vingercoördinaten op het scherm ook maar 1 pixel veranderen - zo blijven alle visuals actueel! Echter, wanneer je vinger meerdere pixels per milliseconde beweegt, moet de tablet enorm snel deze code lezen en uitvoeren - we merkten al snel dat we tegen het limiet van de tablet processor aan liepen. Om de tablet processor te ontlasten en een optimale Frames Per Second te behouden gebruikten we de throttle functie van Lodash. Throttle limiteert hoe vaak per milliseconde een functie mag afgaan, en zodoende kon het helpen in het besparen van processor belasting van de tablet.

Wanneer de gebruiker klaar is met het tekenen van een nieuwe lijn en het scherm loslaat, worden de laatst bekende coördinaten van dit projection line component toegevoegd aan de lines state (Weet je nog? De array die de collectie van reeds getekende lijnen bevat), waarna het projectielijn-component geleegd wordt en verdwijnt - het is vanaf nu als het ware omgedoopt tot een ‘officieel’ getekend lijnstuk!

3. Het 'getekende lijnen' component

Om de gebruiker te tonen welke route lijnen tot dusver zijn getekend, is het van belang om de reeds getekende lijnen, opgeslagen in de lines state, te visualiseren. Dit component visualiseert in het licht grijs deze reeds getekende route lijnen. Precies zoals bij het visualiseren van muren gaat, gebruiken we de ‘.map’ array methode - maar nu toegepast op de array in de lines state.

Slim tekenen & muur interactie

Onze UX-ers bedachten verschillende ‘slimme’ teken features, die het makkelijk maken om als gebruiker routes te tekenen welke parallel zijn aan (diagonale) muren, of welke stoppen wanneer ze op het punt staan om door een muur te tekenen. Om dit te realiseren heeft ons development team een methode bedacht die tijdens touch-gebaren continu checked of de coördinaten van de route-lijn-in-wording eigenschappen vertonen van een nabijliggend muursegment. Zo wordt gechecked of de relatieve afstand van de routelijn en het muursegment, of de hoek van routelijn en het muursegment overeenkomen. Wanneer dit het geval is grijpt de app in, en ‘snapped’ deze de route lijn parallel langs het desbetreffende muursegment. In deze situatie worden de coördinaten, en dus de hoek, van de getekende lijn aangepast op basis van de coördinaten en hoek van het nabije muursegment.

Op vergelijkbare wijze checkt de app tijdens een touch-actie ook of de route-lijn-in-wording een specifiek muursegment doorkruist. Om te voorkomen dat de robot door een muur poogt te rijden en om de gebruiker te helpen een precieze route te tekenen, herkent de app deze situatie: het snijpunt van de routelijn en het muursegment wordt berekend en de routelijn wordt ingekort tot deze een gepaste afstand van het muursegment heeft.

Een geslaagde route

We hopen dat je wat hebt opgestoken door dit artikel en dat je in de basis een idee hebt gekregen van onze opzet voor het route tekenen via touch gestures op een React Native tablet applicatie. Heb je meer vragen over deze opzet, of denk je misschien: dit kan op een betere manier! Laat het ons vooral weten!


Dylan Jongbloed

Developer @Humanoids