Comment nous avons développé l'application AR pour examiner les lieux historiques



Récemment, nous avons combiné des technologies anciennes avec des technologies modernes, ce qui en est ressorti sous la coupe.

Réalité augmentée


Les applications de réalité augmentée en tant que guides de la ville sont un thème bien connu et mis en œuvre par de nombreux développeurs. Cette direction d'utilisation de la RA a été l'une des premières, car elle vous permet d'utiliser toutes les possibilités évidentes de la réalité augmentée: montrer aux utilisateurs des informations sur les bâtiments, donner des informations sur le travail de l'institution et se familiariser avec les sites. Lors du dernier hackathon, qui a eu lieu au sein de l'entreprise, plusieurs projets utilisant la réalité augmentée ont été présentés, et nous avons eu l'idée de créer une application AR qui montrera à quoi ressemblait un point de repère ou un lieu historique dans le passé. Pour ce faire, combinez les technologies modernes de réalité augmentée avec des photographies anciennes. Par exemple, face à la cathédrale Saint-Isaac, vous pouvez pointer une caméra de smartphone vers lui et voir son premier bâtiment en bois, qui a été démoli en 1715.

La mécanique du travail est la suivante: l'application affiche sur la carte les lieux et sites historiques donnés de la ville, affiche de brèves informations à leur sujet, à l'aide de notifications, informe l'utilisateur qu'il est situé près d'un point intéressant. Lorsqu'une personne s'approche d'un monument historique à une distance de 40 mètres, le mode AR devient disponible. Dans le même temps, la caméra s'ouvre et de brèves informations sur les objets s'affichent directement dans l'espace entourant l'utilisateur. Ce dernier a la capacité d'interagir avec des objets virtuels: en touchant la carte d'un lieu historique, vous pouvez procéder à la visualisation de l'album avec des images.

Il semblerait que l'application soit très simple, mais même ici, il y avait des pièges. Je ne vous ennuierai pas avec une histoire sur la mise en œuvre de choses triviales comme le téléchargement de données à partir d'un serveur ou l'affichage de points sur une carte, je vais aller directement aux fonctions qui ont causé les problèmes.

Problème 1. Points flottants


Ainsi, la première chose à faire était de placer des points marqueurs dans l'espace en fonction de l'emplacement réel des lieux historiques par rapport à l'emplacement actuel et à la direction du regard de l'utilisateur.

Pour commencer, nous avons décidé d'utiliser la bibliothèque déjà préparée pour iOS: ARKit-CoreLocation . Le projet repose sur GitHub dans le domaine public, contient, en plus du code des classes principales, des exemples d'intégration et nous permet de terminer la tâche d'intérêt en quelques heures. Il suffit de renseigner dans la bibliothèque les coordonnées des points et l'image utilisée comme marqueur.

Sans surprise, cette facilité devait être payée. Les points marqueurs flottaient constamment dans l'espace: ils montaient jusqu'au plafond ou étaient tirés quelque part sous le pied. Tous les utilisateurs n'accepteraient pas de capturer l'objet AR mis au point pendant plusieurs minutes pour se familiariser avec les informations qui l'intéressent.

Il s'est avéré que beaucoup étaient confrontés à ce bogue de bibliothèque, mais aucune solution n'a encore été trouvée. Le code sur GitHub, malheureusement, n'a pas été mis à jour depuis plus de six mois, j'ai donc dû le contourner.

Nous avons essayé d'utiliser l'altitude au lieu de l'altitude fixe en coordonnées, que le LocationManager a renvoyée pour la position actuelle de l'utilisateur. Cependant, cela n'a pas complètement éliminé le problème. Les données provenant de Location Manager ont commencé à bondir avec une propagation allant jusqu'à 60 mètres, dès que l'appareil a été tordu à la main. En conséquence, l'image était instable, ce qui, bien sûr, ne nous convenait plus.

En conséquence, il a été décidé d'abandonner la bibliothèque ARKit-CoreLocation et de placer des points dans l'espace par eux-mêmes. L'article ARKit et CoreLocation, écrit par Christopher Web-Orenstein, y a beaucoup aidé. J'ai dû passer un peu plus de temps et rafraîchir certains aspects mathématiques dans ma mémoire, mais le résultat en valait la peine: les objets AR étaient enfin à leur place. Après cela, il ne reste plus qu'à les disperser le long de l'axe Y pour que les étiquettes et les points soient plus faciles à lire, et à établir une correspondance entre la distance de la position actuelle au point et la coordonnée Z de l'objet AR, de sorte que les informations sur les lieux historiques les plus proches soient au premier plan.

Il était nécessaire de calculer la nouvelle position de SCNNode dans l'espace, en se concentrant sur les coordonnées:

let place = PlaceNode() let locationTransform = MatrixHelper.transformMatrix(for: matrix_identity_float4x4, originLocation: curUserLocation, location: nodeLocation, yPosition: pin.yPos, shouldScaleByDistance: false) let nodeAnchor = ARAnchor(transform: locationTransform) scene.session.add(anchor: nodeAnchor) scene.scene.rootNode.addChildNode(place) 

Les fonctions suivantes ont été ajoutées à la classe MatrixHelper:

 class MatrixHelper { static func transformMatrix(for matrix: simd_float4x4, originLocation: CLLocation, location: CLLocation, yPosition: Float) -> simd_float4x4 { let distanceToPoint = Float(location.distance(from: originLocation)) let distanceToNode = (10 + distanceToPoint/1000.0) let bearing = GLKMathDegreesToRadians(Float(originLocation.coordinate.direction(to: location.coordinate))) let position = vector_float4(0.0, yPosition, -distanceToNode, 0.0) let translationMatrix = MatrixHelper.translationMatrix(with: matrix_identity_float4x4, for: position) let rotationMatrix = MatrixHelper.rotateAroundY(with: matrix_identity_float4x4, for: bearing) let transformMatrix = simd_mul(rotationMatrix, translationMatrix) return simd_mul(matrix, transformMatrix) } static func translationMatrix(with matrix: matrix_float4x4, for translation : vector_float4) -> matrix_float4x4 { var matrix = matrix matrix.columns.3 = translation return matrix } static func rotateAroundY(with matrix: matrix_float4x4, for degrees: Float) -> matrix_float4x4 { var matrix : matrix_float4x4 = matrix matrix.columns.0.x = cos(degrees) matrix.columns.0.z = -sin(degrees) matrix.columns.2.x = sin(degrees) matrix.columns.2.z = cos(degrees) return matrix.inverse } } 

Pour calculer l'azimut ajouté l'extension CLLocationCoordinate2D

 extension CLLocationCoordinate2D { func calculateBearing(to coordinate: CLLocationCoordinate2D) -> Double { let a = sin(coordinate.longitude.toRadians() - longitude.toRadians()) * cos(coordinate.latitude.toRadians()) let b = cos(latitude.toRadians()) * sin(coordinate.latitude.toRadians()) - sin(latitude.toRadians()) * cos(coordinate.latitude.toRadians()) * cos(coordinate.longitude.toRadians() - longitude.toRadians()) return atan2(a, b) } func direction(to coordinate: CLLocationCoordinate2D) -> CLLocationDirection { return self.calculateBearing(to: coordinate).toDegrees() } } 

Problème 2. Excès d'objets AR


Le problème suivant que nous avons rencontré était une énorme quantité d'objets AR. Il y a beaucoup de lieux et de sites historiques dans notre ville, donc dés avec des informations fusionnées et rampées les unes sur les autres. Il serait très difficile pour l'utilisateur de distinguer une partie des inscriptions, ce qui pourrait faire une impression répugnante. Après avoir discuté, nous avons décidé de limiter le nombre d'objets AR affichés simultanément, en ne laissant que des points dans un rayon de 500 mètres de l'emplacement actuel.

Cependant, dans certaines régions, la concentration des points était encore trop élevée. Par conséquent, pour augmenter la visibilité, ils ont décidé d'utiliser le clustering. Sur l'écran de la carte, cette fonctionnalité est disponible par défaut en raison de la logique intégrée à MapKit, mais en mode AR, il était nécessaire de l'implémenter manuellement.

Le regroupement était basé sur la distance entre la position actuelle et la cible. Ainsi, si le point tombait dans la zone avec un rayon égal à la moitié de la distance entre l'utilisateur et l'attraction précédente de la liste, il se cachait simplement et faisait partie du cluster. À mesure que l'utilisateur s'en approchait, la distance diminuait et le rayon de la zone d'amas diminuait en conséquence, de sorte que les vues situées à proximité ne se confondaient pas en amas. Pour distinguer visuellement les clusters des points uniques, nous avons décidé de changer la couleur du marqueur et d'afficher le nombre d'objets en AR au lieu du nom du lieu.

image

Pour garantir l'interactivité des objets AR, un UITapGestureRecognizer a été suspendu sur l'ARSCNView et dans le gestionnaire à l'aide de la méthode hitTest, il a vérifié sur quels objets SCNNode l'utilisateur avait cliqué. S'il s'avérait être une photographie d'attractions à proximité, l'application ouvrait l'album correspondant en mode plein écran.

Problème 3. Radar


Lors de la mise en œuvre de l'application, il a fallu afficher les points sur un petit radar. En théorie, il ne devrait pas y avoir de malentendus, car nous avons déjà calculé l'azimut et la distance au point, même réussi à les convertir en coordonnées 3D. Il ne restait plus qu'à placer les points dans un espace bidimensionnel sur l'écran.



Afin de ne pas réinventer la roue, ils se sont tournés vers la bibliothèque Radar , dont le code open source a été publié sur GitHub. Les aperçus vifs et les paramètres flexibles de l'exemple étaient encourageants, mais en réalité les points ont été décalés par rapport à la véritable position dans l'espace. Après avoir passé un certain temps à essayer de corriger les formules, nous nous sommes tournés vers l'option moins belle, mais plus fiable décrite dans la boîte à outils de réalité augmentée pour iPhone :

 func place(dot: Dot) { var y: CGFloat = 0.0 var x: CGFloat = 0.0 if degree < 0 { degree += 360 } let bearing = dot.bearing.toRadians() let radius: CGFloat = 60.0 // radius of the radar view if (bearing > 0 && bearing < .pi / 2) { //the 1 quadrant of the radar x = radius + CGFloat(cosf(Float((.pi / 2) - bearing)) * Float(dot.distance)) y = radius - CGFloat(sinf(Float((.pi / 2) - bearing)) * Float(dot.distance)) } else if (bearing > .pi / 2.0 && bearing < .pi) { //the 2 quadrant of the radar x = radius + CGFloat(cosf(Float(bearing - (.pi / 2))) * Float(dot.distance)) y = radius + CGFloat(sinf(Float(bearing - (.pi / 2))) * Float(dot.distance)) } else if (bearing > .pi && bearing < (3 * .pi / 2)) { //the 3 quadrant of the radar x = radius - CGFloat(cosf(Float((3 * .pi / 2) - bearing)) * Float(dot.distance)) y = radius + CGFloat(sinf(Float((3 * .pi / 2) - bearing)) * Float(dot.distance)) } else if (bearing > (3 * .pi / 2.0) && bearing < (2 * .pi)) { //the 4 quadrant of the radar x = radius - CGFloat(cosf(Float(bearing - (3 * .pi / 2))) * Float(dot.distance)) y = radius - CGFloat(sinf(Float(bearing - (3 * .pi / 2))) * Float(dot.distance)) } else if (bearing == 0) { x = radius y = radius - CGFloat(dot.distance) } else if (bearing == .pi / 2) { x = radius + CGFloat(dot.distance) y = radius } else if (bearing == .pi) { x = radius y = radius + CGFloat(dot.distance) } else if (bearing == 3 * .pi / 2) { x = radius - CGFloat(dot.distance) y = radius } else { x = radius y = radius - CGFloat(dot.distance) } let newPosition = CGPoint(x: x, y: y) dot.layer.position = newPosition 

Backend


Il reste à résoudre le problème du stockage des points et des photos. À ces fins, il a été décidé d'utiliser Contentful, et dans la mise en œuvre actuelle du projet, il nous convenait parfaitement.


Au moment du développement de l'application mobile, tous les bekenders étaient engagés dans des projets commerciaux, et content ont permis de prévoir plusieurs heures:

  • développeur mobile - backend pratique
  • gestionnaire de contenu - une zone d'administration pratique pour remplir les données

La mise en œuvre similaire du backend a été à l'origine utilisée par les équipes qui ont participé au hackathon (mentionné au début de l'article), ce qui prouve une fois de plus que des choses comme les hackathons vous permettent d'échapper à la résolution de vos problèmes urgents sur les projets, permettent de recréer et d'essayer quelque chose flambant neuf.

Conclusion



C'était très intéressant de développer une application AR , dans le processus, nous avons essayé plusieurs bibliothèques prêtes à l'emploi, mais nous avons également dû nous souvenir des mathématiques et écrire beaucoup de choses nous-mêmes.

Simple, à première vue, le projet a nécessité de nombreuses heures de travail pour implémenter et affiner les algorithmes, malgré le fait que nous ayons utilisé le SDK standard d'Apple.

Nous avons récemment publié l'application dans l' AppStore . Voici à quoi cela ressemble au travail.


Jusqu'à présent, dans notre base de données, il n'y a des points que pour Taganrog, cependant, tout le monde peut participer à l'extension de la «zone de couverture».

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


All Articles