Comment Yandex a créé la réalité augmentée dans Maps pour iOS. Expérience avec ARKit

Il reste moins de gens qui peuvent être surpris par la réalité augmentée (RA). Pour certains, cette technologie est associée à un jouet pendant quelques heures. D'autres trouvent cela plus pratique.


Je m'appelle Dmitry et je développe Yandex.Maps pour iOS. Aujourd'hui, je vais dire aux lecteurs de Habr comment nous avons créé le routage en utilisant la réalité augmentée. Vous découvrirez également les fonctionnalités de l'utilisation du framework ARKit, grâce auxquelles l'introduction de la réalité augmentée n'est plus du ressort des seuls spécialistes du domaine de la vision par ordinateur.



En 2009, le magazine Esquire a été le premier parmi les médias à ajouter un support de réalité augmentée à son produit. Sur la couverture du magazine, vous avez affiché un code avec lequel vous pouvez voir Robert Downey Jr. "en direct".



L'utilisation de la RA dans l'industrie du divertissement ne se limitait pas à cela. Un exemple frappant est le jeu Pokemon Go, sorti en 2016. En juillet de la même année, il avait été téléchargé plus de 16 millions de fois. Le succès du jeu a conduit à l'émergence de nombreux clones avec AR.


Les événements importants dans l'industrie de la RA au cours des dernières années peuvent être considérés comme les annonces de Google Glass et de Microsoft Hololens. L'apparition de tels appareils montre le vecteur dans lequel évoluent les grandes entreprises.


Apple n'a pas fait exception. En 2017, la société a introduit le cadre ARKit, dont l'importance pour l'industrie ne peut guère être surestimée. Et nous en parlerons plus en détail.


ARKit


Caractéristiques d'ARKit, facilitant l'utilisation de l'AR:


  • absence de besoin d'étiquettes spéciales (marqueurs),
  • intégration avec les infrastructures graphiques Apple 2D / 3D existantes - SceneKit, SpriteKit, Metal,
  • haute précision dans la détermination de la position et de l'orientation de l'appareil dans l'espace,
  • pas besoin de calibrer la caméra ou les capteurs.

Sous le capot d'ARKit se trouve un système d'odométrie visuelle inertielle qui combine les données avec les sous-systèmes visuel (caméra) et inertiel (accéléromètre, gyroscope) de l'appareil pour déterminer la position et le déplacement sur la scène. L'élément de connexion de ce système est le filtre de Kalman - un algorithme qui sélectionne à chaque instant le meilleur des lectures des deux sous-systèmes et nous le fournit sous la forme de notre position et de notre orientation sur la scène. ARKit a également une «compréhension» de la scène - nous pouvons définir des surfaces horizontales et verticales, ainsi que les conditions d'éclairage de la scène. Ainsi, lors de l'ajout d'un objet à la scène, nous pouvons lui ajouter un éclairage par défaut, grâce auquel l'objet sera plus réaliste.


Au fait

Bientôt, la version 2.0 du framework sera publiée, dans laquelle de nouvelles fonctionnalités seront ajoutées et la précision de positionnement sera considérablement améliorée.


ARKit a permis aux développeurs d'intégrer la réalité augmentée de haute qualité dans leurs applications tout en dépensant beaucoup moins d'efforts. Nous allons le démontrer en utilisant l'exemple de Yandex.Maps.


Routage avec AR sur Yandex.Maps


Habituellement, après l'annonce de la nouvelle version d'iOS, de nombreuses équipes de Yandex se réunissent pour discuter de la possibilité d'introduire de nouvelles fonctionnalités dans leurs applications. L'équipe Yandex.Mart a fait de même. Dans le mois qui a suivi l'annonce de ARKit, nous avons souvent discuté de la façon de l'implémenter dans Maps. Quel genre d'idées nous n'avons pas entendues les unes des autres! Assez rapidement, nous sommes arrivés à la conclusion que l'une des solutions les plus utiles et les plus en surface est l'utilisation de la réalité augmentée dans le routage.


Le choix de cette idée est dû au fait que de nombreux utilisateurs de cartes rencontrent souvent une situation où vous vous trouvez dans une zone inconnue et que vous devez décider rapidement où aller. L'approche standard pour l'utilisateur moyen de la carte consiste à ouvrir l'application, à construire un itinéraire pour piétons et, en tournant sur place, à déterminer où se déplacer. L'idée d'introduire la réalité augmentée dans le routage piéton est de sauver l'utilisateur d'actions inutiles, montrant immédiatement où vous devez vous déplacer directement au-dessus de l'image de la caméra.


Tout d'abord, je veux dire quelques mots sur le routage. Qu'est-ce que je mets dans ce concept? Du point de vue de l'implémentation dans une application mobile, il s'agit d'un ensemble d'étapes assez standard qui permettent à l'utilisateur de passer du point A au point B:


  • sélection des points de départ et d'arrivée,
  • recevoir un itinéraire sous forme d'un ensemble de points en coordonnées géographiques (latitude, longitude),
  • affichage sur la carte de la ligne de route,
  • accompagner l'utilisateur avec des informations supplémentaires tout en se déplaçant le long de l'itinéraire.

Nous ne nous attarderons pas sur les deux premiers points. Je peux seulement dire que nous obtenons l'itinéraire via notre bibliothèque multiplateforme Yandex.Mapkit, qui est également à votre disposition sous la forme d'un pod. En quoi le routage en réalité augmentée diffère-t-il du routage standard dans les cartes? Tout d'abord, la principale différence est une carte presque complètement cachée. L'accent principal est mis sur la zone d'écran avec l'image du flux vidéo de la caméra, sur laquelle des éléments visuels supplémentaires sont superposés (marque d'arrivée, marque auxiliaire et image de la ligne de route). Chacun de ces éléments visuels a sa propre charge sémantique et sa propre logique (quand et comment il doit être affiché). Nous examinerons le rôle de chacun de ces éléments plus en détail plus tard, mais pour l'instant je propose d'examiner les tâches que nous avions devant nous:


  • apprendre à positionner des objets sur la scène ARKit, en connaissant leurs coordonnées géographiques,
  • Apprenez à dessiner l'interface utilisateur nécessaire sur une scène 3D avec des performances suffisantes.

Nous devions convertir les coordonnées des points géographiques des coordonnées de la scène, sélectionner les points à afficher et afficher toutes les interfaces utilisateur nécessaires au-dessus de l'image de la caméra dans la bonne position. Mais tout s'est avéré un peu plus compliqué qu'il n'y paraissait à première vue.


Avant de commencer à implémenter les fonctionnalités directement, un de mes collègues a été chargé de créer un prototype montrant la possibilité (ou l'impossibilité) de mettre en œuvre des fonctionnalités similaires avec un ensemble d'outils accessibles. Pendant deux semaines, nous avons regardé San Sanych labourer les espaces ouverts de l'espace ouvert et les environs proches de notre bureau avec un téléphone à la main et regarder le monde autour de nous à travers le prisme de la caméra. En conséquence, nous avons obtenu un prototype fonctionnel qui montrait chaque point de l'itinéraire comme une marque sur la scène avec une distance jusqu'à lui. Avec l'aide de ce prototype, il a été possible, dans une combinaison réussie de circonstances, de se rendre du travail au métro et de ne jamais se perdre. Mais sérieusement, il a confirmé la possibilité de mettre en œuvre la fonctionnalité prévue. Mais il restait un certain nombre de tâches que notre équipe devait encore résoudre.


Tout a commencé par l'étude des outils. À cette époque, une seule personne de l'équipe avait de l'expérience avec les graphiques 3D. Jetons un coup d'œil aux outils que toute personne qui envisage de mettre en œuvre de telles idées avec ARKit devra utiliser.


Outils et API


La tâche principale du rendu des objets consiste à créer et à gérer les objets de scène du framework SceneKit. Avec l'avènement d'ARKit, la classe ARSCNView (le descendant de la classe SCNView - la classe de base pour travailler avec la scène dans SceneKit) est devenue disponible pour le développeur, ce qui résout la plupart des tâches fastidieuses d'intégration d'ARKit et de SceneKit, à savoir:


  • synchronisation de la position du téléphone dans l'espace avec la position de la caméra sur la scène,
  • le système de coordonnées de la scène coïncide avec le système de coordonnées ARKit,
  • comme arrière-plan de la scène, le flux vidéo de la caméra de l'appareil est utilisé.

L'objet ARSCNView fournit également au développeur un objet de session de réalité augmentée qui peut être démarré avec la configuration nécessaire, arrêté ou abonné à divers événements à l'aide de l'objet délégué.


Pour ajouter des objets à la scène, des héritiers ou directement des objets SCNNode sont utilisés. Cette classe représente une position (vecteur tridimensionnel) dans le système de coordonnées de son parent. Ainsi, nous obtenons un arbre d'objets sur la scène avec une racine dans un objet spécial - le rootNode de notre scène. Tout ici est très similaire à la hiérarchie des objets UIView dans UIKit. Les objets SCNNode peuvent être affichés sur scène lorsqu'ils ajoutent de la matière et de l'éclairage.


Afin d'ajouter de la réalité augmentée à une application mobile, vous devez également connaître les principaux objets de l'API ARKit. Le principal est l'objet de la session de réalité augmentée - ARSession. Cet objet effectue le traitement des données et est responsable du cycle de vie de la session de réalité augmentée. Le but de cet article n'est pas de redire la documentation d'ARKit et SceneKit, donc je n'écrirai pas sur tous les paramètres de configuration disponibles de la session de réalité augmentée, mais je me concentrerai sur l'un des paramètres les plus importants de la configuration de la session de réalité augmentée pour les applications de navigation - worldAlignment. Ce paramètre détermine la direction des axes de la scène au moment de l'initialisation de la session. En général, lors de l'initialisation d'une session de réalité augmentée, ARKit crée un système de coordonnées avec un début à un point qui coïncide avec la position actuelle du téléphone dans l'espace et dirige l'axe de ce système en fonction de la valeur de la propriété woldAlignment. Dans notre implémentation, la valeur gravityAndHeading est utilisée, ce qui implique que les axes seront dirigés comme suit: l'axe Y - dans la direction opposée à la gravité, l'axe Z - au sud et l'axe X - à l'est.


alignement du monde-gravité-et-cap


Avec une bonne combinaison de circonstances, les axes X / Z seront en effet alignés avec les directions vers le sud / est, mais, en raison d'erreurs dans les relevés de la boussole, les axes peuvent être dirigés selon un certain angle par rapport à la direction décrite dans la documentation. C'est l'un des problèmes auxquels nous avons dû faire face, mais nous en reparlerons plus tard.


Maintenant que nous avons examiné les outils de base, résumons: mapper un itinéraire à l'aide de SceneKit ajoute des objets SCNNode à la scène aux positions obtenues en convertissant des coordonnées géographiques en coordonnées de scène. Avant de parler de conversion de coordonnées et de manière générale de placer des objets sur la scène, parlons des problèmes de rendu des éléments d'interface utilisateur, en supposant que nous connaissons la position des objets sur la scène.


Marque d'arrivée


L'élément visuel principal du parcours piéton à réalité augmentée est la marque d'arrivée, qui affiche le point final de l'itinéraire. Également au-dessus de la marque, nous montrons à l'utilisateur la distance jusqu'au point final de l'itinéraire.


finition-repère-aperçu


La taille


Lorsque la conception de cette étiquette nous a été présentée pour la première fois, nous avons d'abord fait attention aux exigences relatives à la taille de cette étiquette. Ils n'ont pas obéi aux règles de projection en perspective. J'expliquerai que dans les moteurs tridimensionnels qui sont utilisés pour créer, par exemple, des jeux informatiques, le «look» est modélisé en utilisant la projection en perspective. Selon les règles de projection en perspective, les objets distants sont représentés à plus petite échelle et les lignes parallèles ne sont généralement pas parallèles. Ainsi, la taille de projection de l'objet sur le plan de l'écran change linéairement (diminue) lorsque la caméra s'éloigne de l'objet sur la scène. Il résulte de la description des dispositions que la taille de la marque sur l'écran a une taille fixe (maximale) lorsqu'elle est supprimée à moins de 50 m, puis diminue linéairement de 50 m à 2 km, après quoi la taille minimale reste inchangée. De telles exigences sont évidemment dues à la commodité de l'utilisateur. Ils permettent à l'utilisateur de ne jamais perdre le point final de l'itinéraire de la vue, de sorte que l'utilisateur aura toujours une idée de l'endroit où se déplacer.


finition-repère-taille-exigences


Nous devions comprendre comment nous pouvions nous insérer dans le mécanisme de projection SceneKit qui fonctionnait selon certaines règles. Je veux tout de suite noter que nous avions environ deux semaines pour tout faire sur tout, donc nous n'avions tout simplement pas le temps de procéder à une analyse approfondie des différentes approches pour résoudre les problèmes posés. Maintenant, analyser nos décisions, les évaluer est beaucoup plus simple, et nous pouvons conclure que la plupart des décisions prises étaient correctes. L'exigence de taille, en fait, a été la première pierre d'achoppement. Tous les problèmes décrits ci-dessous peuvent être résolus à l'aide de SceneKit et UIKit. J'ai essayé d'expliquer en détail comment résoudre chacun des problèmes en utilisant les deux approches. Quelle approche utiliser dépend de vous.


Imaginons que nous avons décidé d'implémenter une étiquette de finition à l'aide de SceneKit. Si nous prenons en compte que l'étiquette selon les dispositions aurait dû ressembler à un cercle à l'écran, alors il devient évident que dans SceneKit l'objet étiquette devrait être une sphère (puisque la projection de la sphère sur n'importe quel plan est un cercle). Pour que la projection ait un certain rayon sur l'écran, défini dans les exigences des concepteurs, il est nécessaire de connaître le rayon de la sphère à chaque instant. Ainsi, en plaçant une sphère d'un certain rayon sur la scène à un certain point et en mettant constamment à jour son rayon à l'approche ou en s'éloignant, nous obtiendrons une projection sur l'écran de la taille requise à tout moment. L'algorithme de détermination du rayon de la sphère à un instant arbitraire est le suivant:


  1. définir la position de l'objet sur la scène - le centre de la sphère,
  2. trouver la projection de ce point sur le plan de l'écran (à l'aide de l'API SceneKit),
  3. pour déterminer la taille requise de la marque sur l'écran, on trouve la distance de la caméra au centre de la sphère sur la scène,
  4. nous déterminons la taille requise sur l'écran par la distance à l'objet en utilisant les règles décrites dans la conception,
  5. connaissant la taille de la marque sur l'écran (diamètre du cercle), on choisit n'importe quel point sur ce cercle,
  6. faire la projection inverse (unfojectPoint) du point sélectionné,
  7. nous trouvons la longueur du vecteur du point reçu sur la scène au centre de la sphère.

La valeur obtenue de la longueur du vecteur sera le rayon souhaité de la sphère.


finition-repère-taille-solution-scenekit


Au moment de la mise en œuvre, nous n'avons pas pu trouver un moyen de déterminer la taille de l'objet sur la scène, et nous avons décidé de tracer la marque d'arrivée à l'aide d'UIKit. Dans ce cas, l'algorithme répète les étapes 1 à 5, après quoi un cercle de la taille souhaitée est dessiné à l'écran avec le centre au point obtenu à l'étape 2 à l'aide des outils UIKit. Un exemple d'implémentation d'une étiquette utilisant UIKit peut être trouvé ici .


Quelques mots sur le code

À la fin de l'article, j'ai donné plusieurs liens vers des matériaux utiles et simplement intéressants, y compris des exemples, dans lesquels vous pouvez regarder en détail le vrai code qui résout les problèmes présentés dans l'article et implémente les algorithmes présentés. Le principal intérêt à mon avis est le prototype du routage piéton , qui regroupe toutes les fonctionnalités, à l'exception du mécanisme de réglage des axes, qui est décrit en détail ci-dessous.


Le code ci-dessus ne prétend pas être l'optimalité, l'exhaustivité et la qualité de production =)


La différence entre l'utilisation de SceneKit et UIKit dans ce cas réside également dans le fait que lors de l'implémentation sur SceneKit, l'objet SCNNode pour le point de terminaison de l'itinéraire (marque de fin) sera créé avec le matériau et la géométrie, car il doit être visible, lors de l'utilisation d'UIKit. nous avons besoin de l'objet nœud exclusivement pour rechercher la projection sur le plan de l'écran (pour déterminer le centre de la marque sur l'écran). Dans ce cas, la géométrie et le matériau n'ont pas besoin d'être ajoutés. Notez que la distance de la caméra à l'objet SCNNode du point final de l'itinéraire peut être trouvée de deux manières - en utilisant les coordonnées géographiques des points, ou comme la longueur du vecteur entre les points sur la scène. Cela est possible car l'objet caméra est une propriété SCNNode. Pour obtenir le nœud de la caméra, vous devez vous référer à la propriété pointOfView de notre scène.


Nous avons appris à déterminer le rayon du nœud de la marque d'arrivée à un moment arbitraire lors de l'implémentation sur SceneKit et la position de la vue de la marque d'arrivée si elle est implémentée sur UIKit. Reste à comprendre quand il faut mettre à jour ces valeurs? Cet endroit est la méthode d'objet SCNSceneRendererDelegate:


renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) 

Cette méthode est appelée après chaque image de scène rendue. En mettant à jour les valeurs des propriétés dans le corps de cette méthode, nous obtenons une étiquette de finition correctement affichée.


L'animation


Après que la marque d'arrivée soit apparue dans dev, nous avons ajouté une animation d'ondulation à cette marque. Je pense que pour la plupart des développeurs iOS, la création d'animations n'est pas un gros problème. Mais en réfléchissant à la méthode d'implémentation, nous avons rencontré le problème de mettre constamment à jour le cadre de notre vue. Notez que dans la plupart des cas, des animations sont ajoutées aux objets UIView statiques. Un problème similaire - une mise à jour constante du rayon de la géométrie du nœud se pose lors de l'implémentation à l'aide de SceneKit. Le fait est que l'animation pulsée se résume à l'animation de la taille du cercle (pour UIKit) et du rayon de la sphère (pour SceneKit). Oui, oui, nous savons que dans UIKit ce type d'animation peut être fait en utilisant CALayer, mais pour la simplicité de la narration, j'ai décidé de considérer ce problème symétriquement pour les deux frameworks. Considérons une implémentation sur UIKit. Si vous ajoutez du code qui anime le même cadre au code existant qui met à jour le cadre de vue, l'animation sera interrompue en définissant explicitement le cadre. Par conséquent, comme solution à ce problème, nous avons décidé d'utiliser l'animation de la propriété transform.scale.xy de l'objet UIView. Lors de l'implémentation à l'aide de SceneKit, vous devrez ajouter une animation de la propriété scale à l'objet SCNNode. La bonne chose à propos de l'utilisation de SceneKit dans ce cas est le fait qu'il prend entièrement en charge CoreAnimation, donc l'apprentissage d'une nouvelle API n'est pas nécessaire. Le code qui implémente une animation similaire à l'animation d'étiquette dans Yandex.Maps ressemble à ceci:


 let animationGroup = CAAnimationGroup.init() animationGroup.duration = 1.0 animationGroup.repeatCount = .infinity let opacityAnimation = CABasicAnimation(keyPath: "opacity") opacityAnimation.fromValue = NSNumber(value: 1.0) opacityAnimation.toValue = NSNumber(value: 0.1) let scaleAnimation = CABasicAnimation(keyPath: "scale") scaleAnimation.fromValue = NSValue(scnVector3: SCNVector3(1.0, 1.0, 1.0)) scaleAnimation.toValue = NSValue(scnVector3: SCNVector3(1.2, 1.2, 1.2)) animationGroup.animations = [opacityAnimation, scaleAnimation] finishNode.addAnimation(animationGroup, forKey: "animations") 

Panneau d'affichage


Au début de l'article, j'ai mentionné un panneau d'affichage avec une distance jusqu'au point final de l'itinéraire, qui, en substance, est une étiquette avec du texte toujours située au-dessus de la marque d'arrivée. Par tradition, je décrirai les problèmes inhérents aux implémentations sur UIKit et SceneKit, expliquant les solutions possibles pour chacun des frameworks.


Commençons par UIKit. Dans ce cas, le panneau d'affichage est un UILabel régulier, dans lequel le texte est constamment mis à jour, indiquant la distance jusqu'au point final de l'itinéraire. Examinons le problème auquel nous sommes confrontés.


finition-repère-panneau d'affichage-problème-uikit


Si vous définissez une étiquette sur un cadre, puis faites pivoter le téléphone, nous verrons que le cadre ne change pas (il serait étrange que ce ne soit pas le cas). Dans le même temps, nous souhaitons que l'étiquette reste parallèle au plan de la terre.


fini-repère-panneau-d'affichage-désiré-uikit


Je pense que tout le monde comprend que lors du changement d'orientation de l'appareil, nous devons tourner l'étiquette, mais sous quel angle? Si vous excitez l'imagination et imaginez mentalement tous les axes des systèmes de coordonnées et des vecteurs impliqués dans ce processus, nous pouvons conclure que l'angle de rotation est égal à l'angle entre l'axe x du système de coordonnées UIKit et la projection de l'axe X du système de coordonnées SceneKit sur le plan de l'écran.


solution-de-repère-d'affichage-solution-uikit


Une tâche simple qui a une nouvelle fois prouvé l'utilité du cours de géométrie scolaire.


Lors de la mise en œuvre de la marque d'arrivée à l'aide de SceneKit, vous devrez très probablement rendre le panneau d'affichage à distance à l'aide des outils SceneKit, ce qui signifie que vous aurez certainement la tâche de faire en sorte que l'objet SCNNode soit toujours orienté vers la caméra. Je pense que le problème deviendra plus clair si vous regardez l'image:


finish-placemark-billboard-problem-scenekit


Ce problème est résolu en utilisant l'API SCNBillboardConstraint. En ajoutant une constante avec un axe libre Y à la collection des interprétations de notre nœud, nous obtenons un nœud qui tourne autour de l'axe Y de son système de coordonnées, afin d'être toujours orienté vers la caméra. La seule tâche du développeur est de placer ce nœud à la bonne hauteur afin que le panneau d'affichage avec la distance soit toujours visible pour l'utilisateur.


 let billboardConstraint = SCNBillboardConstraint() billboardConstraint.freeAxes = SCNBillboardAxis.Y finishNode.constraints = [billboardConstraint] 

Tag Assistant


L'une des principales caractéristiques du routage piéton à réalité augmentée, à l'intérieur de l'équipe, nous considérons une marque auxiliaire - un élément visuel spécial qui apparaît à l'écran au moment où le point final de l'itinéraire quitte la zone de visibilité et montre à l'utilisateur où tourner le téléphone pour que la marque apparaisse à l'écran ligne d'arrivée.


finition-repère-indice-aperçu


Je suis sûr que de nombreux lecteurs ont rencontré des fonctionnalités similaires dans certains jeux, le plus souvent des tireurs. Quelle surprise notre équipe a été lorsque nous avons vu cet élément d'interface utilisateur dans les mises en page. Je dois dire tout de suite que la mise en œuvre correcte d'une telle fonctionnalité peut nécessiter plus d'une heure d'expérimentation de votre part, mais le résultat final vaut le temps passé. Nous avons commencé par définir les exigences, à savoir:


  • pour n'importe quelle orientation de l'appareil, l'étiquette se déplace le long des bordures de l'écran,
  • si l'utilisateur a tourné de 180 degrés jusqu'au point final de l'itinéraire, l'étiquette est affichée en bas de l'écran,
  • à chaque instant, le virage vers la marque doit être le virage le plus court vers le point final de l'itinéraire.

Après avoir décrit les exigences, nous avons commencé la mise en œuvre. Presque immédiatement, nous sommes arrivés à la conclusion que le rendu se ferait avec UIKit. Le principal problème de la mise en œuvre a été la détermination du centre de ce label à chaque instant. Après avoir considéré la marque d'arrivée, une telle tâche ne devrait pas causer de difficultés, je ne m'attarderai donc pas sur sa solution en détail. Dans l'article, je ne donnerai qu'une description de l'algorithme pour choisir le centre de l'étiquette auxiliaire, et le code source peut être trouvé ici .


Algorithme du centre de recherche Algorithme de recherche:


  1. créer un objet SCNNode pour le point final de l'itinéraire avec une position sur la scène obtenue à partir des coordonnées géographiques du point,
  2. trouver la projection d'un point sur le plan de l'écran,
  3. trouver l'intersection du segment du centre de l'écran au point de la projection trouvée avec les segments des limites de l'écran dans le système de coordonnées de l'écran.

solution-indice-repère-finition


Le point d'intersection trouvé est le centre souhaité de la marque auxiliaire. Par analogie avec le code qui met à jour les paramètres d'étiquette de finition, nous avons placé le code qui rend l'étiquette auxiliaire dans la méthode déléguée déjà exprimée ci-dessus.


Polyligne de routage


Après avoir construit un itinéraire et vu la marque d'arrivée à l'écran, l'utilisateur peut l'atteindre en s'orientant uniquement dans la direction de la marque, mais le routage est appelé ainsi car il montre l'itinéraire vers l'utilisateur. Nous avons pensé qu'il serait très étrange de réduire la fonctionnalité du routage piéton, à l'exclusion de l'affichage de l'itinéraire de la version AR. Pour visualiser la ligne de route, il a été décidé d'afficher un ensemble de flèches se déplaçant le long de celle-ci. Dans ce cas, les concepteurs étaient convaincus que les flèches disparaîtraient pratiquement en s'éloignant (la taille serait déterminée par les règles de projection en perspective), et il a été décidé d'utiliser SceneKit pour la mise en œuvre.


Avant de décrire la mise en œuvre, il est important de noter que, par conception, les flèches auraient dû être à une distance de 3 m les unes des autres. Si vous estimez le nombre d'objets (flèches) qui doivent être restitués avec un parcours d'environ 1 km de long, alors ce sera environ 330 pièces. En même temps, à chaque objet s'ajoute une animation de mouvement le long de sa portion de route. Notez que les flèches éloignées de la position de la caméra sur la scène à une distance d'environ 100-150 mètres sont pratiquement invisibles en raison de leur petite taille. Après avoir pris en compte ces facteurs, il a été décidé de ne pas afficher tous les objets, mais d'afficher uniquement ceux qui sont retirés à l'utilisateur sur une distance maximale de 100 mètres le long de la ligne de route, en mettant à jour périodiquement l'ensemble d'objets affiché. Nous affichons une quantité suffisante d'informations visuelles, éliminant les calculs SceneKit inutiles et économisant la batterie de l'utilisateur.


itinéraire-polyligne-aperçu


Regardons les principales étapes que nous avons dû réaliser pour obtenir le résultat final:


  • sélection de la section de route pour laquelle nous afficherons les primitives,
  • création de modèles 3D,
  • création d'animation
  • mettre à jour lors de la conduite sur un itinéraire.

Sélection d'un tracé à afficher


Comme je l'ai noté ci-dessus, nous n'affichons pas de flèches pour l'ensemble de l'itinéraire, mais sélectionnons la section optimale pour l'affichage. Le choix d'un segment à un moment arbitraire consiste à rechercher le segment d'itinéraire le plus proche (l'itinéraire est une séquence de segments / segments) à la position actuelle de l'utilisateur et à sélectionner des segments de l'itinéraire le plus proche vers le point final de l'itinéraire jusqu'à ce que leur longueur totale dépasse 100 mètres.


route-polyligne-route-sélection-de-partie


Création de modèles 3D


Examinons plus en détail le processus de création d'un modèle 3D. Dans la plupart des cas, tout ce que vous devez faire pour créer un modèle 3D simple (comme notre flèche) est d'ouvrir n'importe quel éditeur 3D, de passer un peu de temps à le maîtriser et à y créer ce modèle. Dans le cas où les gars de votre équipe ont de l'expérience en modélisation 3D, ou s'ils ont le temps d'apprendre, par exemple, 3DMax (et il doit être acheté), alors vous êtes incroyablement chanceux. Malheureusement, au moment de la mise en œuvre de cette fonctionnalité, aucun de nous n'avait d'expérience particulière, il n'y avait pas de temps libre pour la formation, nous avons donc dû créer un modèle, pour ainsi dire, avec des moyens improvisés. Je veux dire la description du modèle dans le code. Tout a commencé par la présentation d'un modèle 3D sous forme de triangles. Ensuite, nous avons dû trouver manuellement les coordonnées des sommets de ces triangles dans le système de coordonnées du modèle, puis créer un tableau d'indices des sommets des triangles. Avec ces données à notre disposition, nous pouvons créer la géométrie nécessaire directement dans SceneKit. Vous pouvez créer un modèle similaire au nôtre, par exemple, comme ceci:


 class ARSCNArrowGeometry: SCNGeometry { convenience init(material: SCNMaterial) { let vertices: [SCNVector3] = [ SCNVector3Make(-0.02, 0.00, 0.00), // 0 SCNVector3Make(-0.02, 0.50, -0.33), // 1 SCNVector3Make(-0.10, 0.44, -0.50), // 2 SCNVector3Make(-0.22, 0.00, -0.39), // 3 SCNVector3Make(-0.10, -0.44, -0.50), // 4 SCNVector3Make(-0.02, -0.50, -0.33), // 5 SCNVector3Make( 0.02, 0.00, 0.00), // 6 SCNVector3Make( 0.02, 0.50, -0.33), // 7 SCNVector3Make( 0.10, 0.44, -0.50), // 8 SCNVector3Make( 0.22, 0.00, -0.39), // 9 SCNVector3Make( 0.10, -0.44, -0.50), // 10 SCNVector3Make( 0.02, -0.50, -0.33), // 11 ] let sources: [SCNGeometrySource] = [SCNGeometrySource(vertices: vertices)] let indices: [Int32] = [0,3,5, 3,4,5, 1,2,3, 0,1,3, 10,9,11, 6,11,9, 6,9,7, 9,8,7, 6,5,11, 6,0,5, 6,1,0, 6,7,1, 11,5,4, 11,4,10, 9,4,3, 9,10,4, 9,3,2, 9,2,8, 8,2,1, 8,1,7] let geometryElements = [SCNGeometryElement(indices: indices, primitiveType: .triangles)] self.init(sources: sources, elements: geometryElements) self.materials = [material] } } static func arrowBlue() -> SCNGeometry { let material = SCNMaterial() material.diffuse.contents = UIColor.blue material.lightingModel = .constant return ARSCNArrowGeometry(material: material) } 

Le résultat final ressemble à ceci:


route-polyligne-flèche-modèle


Animation de ligne de route


L'étape suivante sur la façon d'afficher une ligne animée de l'itinéraire a été l'étape de création de l'animation elle-même. Mais comment réaliser l'animation qui, dans sa forme finale, semble que la flèche commence son mouvement au point de départ de la section sélectionnée de l'itinéraire et "flotte" le long de l'itinéraire jusqu'à la fin de cette section?



Je ne décrirai pas toutes les façons possibles de créer une telle animation, mais je m'attarderai plus en détail sur la méthode que nous avons choisie. Après avoir sélectionné une section de l'itinéraire, nous la divisons en sections de même longueur - sections de l'animation d'une flèche. Chacune de ces sections est mise en évidence en couleur et a une longueur égale à la distance entre les flèches.


partitionnement de route-polyligne-route-part


Au début de chaque section, nous créons l'objet SCNNode de la flèche, dont l'animation consiste à se déplacer le long de sa section.


route-polyline-arrows-initial-position


Comme vous pouvez le voir, la section d'animation se compose parfois d'un segment, parfois de deux ou plus. Tout dépend de l'étape (dans notre cas - 3 mètres) entre les flèches et les coordonnées des points qui composent l'itinéraire.


Une animation de flèche est une séquence de deux étapes:


  • apparition dans la position initiale avec l'angle de rotation initial,
  • une séquence de décalages le long de segments avec des rotations aux points de connexion des segments.

Schématiquement, cela ressemble à ceci:


route-polyline-arrow-anitaion-steps


Il nous a semblé le moyen le plus simple de mettre en œuvre une telle animation à l'aide de l'API SCNAction - une API déclarative qui vous permet de créer facilement des animations séquentielles, de groupe et répétitives. Vous pouvez regarder l'implémentation plus en détail ici . Du fait que chaque flèche termine son animation au point de départ de la section d'animation de la flèche suivante, l'impression d'un mouvement continu de la flèche sur toute la section sélectionnée de l'itinéraire est créée.


Sur ce point, je propose de compléter l'examen de divers aspects du rendu et de passer à la partie principale - déterminer les positions des objets sur la scène par les coordonnées géographiques des objets.


Déterminer la position d'un objet sur la scène


Nous commençons la conversation sur la détermination de la position d'un objet sur la scène en considérant les systèmes de coordonnées, dont la conversion doit être effectuée. Il n'y en a que 2:


  • coordonnées géodésiques (ou géographiques pour simplifier) ​​- la position des objets (points de route) dans le monde réel,
  • Coordonnées cartésiennes - la position des objets sur la scène (dans ARKit). Rappelons que le système de coordonnées de la scène coïncide avec le système de coordonnées ARKit (dans le cas d'utilisation d'ARSCNView).

La traduction d'un système de coordonnées à un autre et vice versa est possible du fait que les coordonnées dans ARKit sont mesurées en mètres, et le décalage entre deux coordonnées géodésiques peut être traduit avec une grande précision dans le décalage en mètres le long des axes X et Z du système de coordonnées ARKit à petits décalages. Permettez-moi de vous rappeler que les coordonnées géodésiques sont des points avec une certaine longitude et latitude.


Rappelons des concepts aussi importants du cours de la géographie que les parallèles et les méridiens, et leurs propriétés de base:


  • Parallèle est une ligne avec un degré de latitude. Les longueurs des différents parallèles sont différentes.
  • Méridien - une ligne avec un degré de longitude. Les longueurs de tous les méridiens sont les mêmes.

Voyons maintenant comment calculer le décalage en mètres, entre deux coordonnées géodésiques avec des coordonnées \ en ligne (lat_1, lon_1) et \ en ligne (lat_2, lon_2) :


\ Delta x = \ Delta lon \ times metresInLonDegree (lat_ {0}) , \ Delta z = \ Delta lat \ times mètresInLatDegree


metresInLonDegree (\ alpha) = \ frac {2 \ pi R_ \ text {lands} \ cos \ left (\ alpha \ right)} {360 ^ {°}} , metresInLatDegree = \ frac {2 \ pi R_ \ text {lands}} {360 ^ {°}}


Explication

Le déplacement en coordonnées géodésiques correspond linéairement aux mètres uniquement pour de petits déplacements. Lors de grands déplacements, il est nécessaire de prendre honnêtement l'intégrale.


Maintenant que nous pouvons traduire le décalage d'un système de coordonnées à un autre, nous devons décider d'un point de référence - un point pour lequel la coordonnée géographique et la coordonnée dans ARKit (coordonnée sur la scène) sont connues en même temps. Après avoir trouvé un tel point, nous pouvons déterminer les coordonnées de n'importe quel objet sur la scène, en connaissant ses coordonnées géographiques et en utilisant les formules ci-dessus.


Pour plus de clarté, considérons un exemple:
Au début de la session de réalité augmentée, nous avons demandé à CoreLocation notre coordonnée géographique et l'avons reçue instantanément - \ en ligne (lat_0, lon_0) . Rappelant le fait que l'origine du système de coordonnées ARKit est au début de la session au point où se trouve l'appareil, nous avons obtenu le point de référence, car nous connaissons la coordonnée géographique et la coordonnée sur la scène \ inline (x_0, y_0, z_0) = (0,0,0) . Essayons de trouver la coordonnée sur la scène de l'objet avec une coordonnée géographique \ en ligne (lat_1, lon_1) . Pour ce faire, recherchez le décalage en mètres entre la coordonnée géographique de l'objet et la coordonnée géographique de notre point de référence, puis ajoutez le décalage trouvé à la coordonnée sur la scène du point de référence. La coordonnée résultante sur la scène sera celle désirée.


coordonnées-conversion-objet-position-sur-scène


Je note que la position sur la scène trouvée de cette manière ne correspondra à la position de l'objet dans le monde réel que si les axes X / Z du système de coordonnées de la scène sont alignés avec les directions vers le sud / est. L'alignement des axes, en théorie, doit être réalisé en définissant l'indicateur worldAlignment sur gravitiAndHeading. Mais comme je l'ai dit au début de l'article, c'est loin d'être toujours le cas.


Examinons plus en détail la méthode de détermination du point de référence. Pour ce faire, nous introduisons le concept d' estimation - un ensemble de coordonnées géographiques et de coordonnées sur la scène.


coordonnées-conversion-estimation-définition


La méthode proposée ci-dessus pour déterminer le point de référence peut ne pas toujours être utilisée. Au moment du début d'une session de réalité augmentée, une demande de CLLocation d'un utilisateur peut ne pas être exécutée immédiatement, de plus, la précision de la coordonnée obtenue peut avoir une grande erreur. Il serait plus correct de demander à SceneKit une position sur la scène au moment où nous obtenons la valeur de CoreLocation. Dans ce cas, les composantes de l'estimation résultante sont en effet obtenues en même temps, et nous avons la possibilité d'utiliser n'importe laquelle des estimations comme point de référence. Lorsque vous travaillez avec ARKit, l'erreur de décalage s'accumule au fil du temps, donc Apple ne recommande pas d'utiliser ARKit comme outil de navigation.


Lorsque nous avons décidé d'implémenter le routage piéton avec la réalité augmentée, nous avons fait une petite recherche des solutions qui existaient à l'époque, en utilisant ARKit pour des tâches similaires, et sommes tombés sur le cadre ARKit + CoreLocation. L'idée de ce cadre était que grâce à ARKit nous pouvons déterminer plus précisément l'emplacement de l'utilisateur que lors de l'utilisation exclusive de CoreLocation.


Concept ARKit + CoreLocation:


  • lors de la réception de CLLocation de CLLocationManager
    • demander une position sur la scène à l'aide de scene.pointOfView.worldPosition
    • enregistrer cette paire de coordonnées (estimation) dans le tampon
  • obtenir l'emplacement exact si nécessaire
    • choisissez la meilleure estimation
    • calculer le décalage entre la position actuelle sur la scène et la position sur la scène de la meilleure estimation
    • appliquer le décalage trouvé à la coordonnée géographique de la meilleure estimation

, , CoreLocation, .


, « ». , .


(, ):


  • ( horizontalAccuracy),
  • ,
  • 100 .

CoreLocation . , , CoreLocation , 100 .


, . , , ( 100 ).



, X/Z ARKit / . ARKit , , .


Pourquoi?

, (, IKEA, ), Y ARKit – , . gravity worldAlignment.


, . , , , . . AR . , , , , . AR.



, . , \inline t_1 CLLocationManager \inline (lat_1,lon_1)\inline (x_1,z_1) . \inline t_2 CLLocationManager — \inline (lat_2,lon_2) \inline (x_2,z_2) en conséquence.


ARKit — \inline (\Delta x,\Delta z) 2 CoreLocation \inline t_2 . \inline (lat_2,lon_2) \inline (lat_{2calc},lon_{2calc}) . , CoreLocation . . ARKit /.


coordinates-conversion-correction-angle-problem


ARKit Y? . :


  1. ,
  2. ,
  3. ,
  4. ,
  5. .

. . CLLocationManager' , ( ), ( ).


?

. , , . , , GPS .


1, 2 : \inline initialBearing(1,2) et \inline initialBearing(1,2_{calc})\inline 2_{calc} – 2, ARKit. \inline initialBearing(a,b) ( Bearing).


coordinates-conversion-correction-angle-calculation-for-pair


. , ? , , , , . , , , horizontalAccuracy. , , , . :


coordinates-conversion-correction-angle-calculation-error


, , .


. , . :


  • N ,
  • ,
  • M ( ?).

, , , , (), . , . , , . , , ( ). .


, . , , ( , , ).


Test


, . , , , . 2 :


  • ,
  • .

- , , , , .


. , , 100 CLLocation, . , , , 10 ( 10 ). ? , "". , . , , , . , , . , CoreLocation. , . , .


. , . , (, ), , 0 . , , .


" ". . , , , , CLLocation, , . ( ) .


, ARKit.


correction-angle-calculation-alg-testing-street-before-correction


, .


correction-angle-calculation-alg-testing-street-after-correction


( 3-4 ) , .


correction-angle-calculation-alg-testing-street-after-last-correction


JS, AR CoreLocation.


correction-angle-calculation-alg-testing-tracks


— gravity worldAlignment . , . .


Au lieu d'une conclusion


Slack, , , , . AR. . AR AppStore 2017 . , .


Liens utiles



, . .

Source: https://habr.com/ru/post/fr421957/


All Articles