
Vor kurzem haben wir alte Technologien mit modernen Technologien kombiniert, was dabei herauskam.
Augmented Reality
Augmented Reality-Apps als Stadtführer sind ein bekanntes Thema und werden von vielen Entwicklern implementiert. Diese Richtung der Verwendung von AR war eine der ersten, da Sie alle offensichtlichen Möglichkeiten der Augmented Reality nutzen können: Zeigen Sie den Benutzern Informationen über Gebäude, geben Sie Informationen über die Arbeit der Institution und machen Sie sich mit den Sehenswürdigkeiten vertraut. Beim letzten Hackathon, der innerhalb des Unternehmens stattfand, wurden mehrere Projekte mit Augmented Reality vorgestellt, und wir kamen auf die Idee, eine AR-Anwendung zu erstellen, die zeigt, wie ein Wahrzeichen oder ein historischer Ort in der Vergangenheit aussah. Kombinieren Sie dazu moderne Augmented Reality-Technologien mit alten Fotografien. Mit Blick auf die Isaakskathedrale können Sie beispielsweise eine Smartphone-Kamera auf ihn richten und sein erstes Holzgebäude sehen, das 1715 abgerissen wurde.
Die Arbeitsmechanismen sind wie folgt: Die Anwendung zeigt die angegebenen historischen Orte und Sehenswürdigkeiten der Stadt auf der Karte an, zeigt kurze Informationen darüber an und benachrichtigt den Benutzer mithilfe von Benachrichtigungen, dass er nicht weit von einem interessanten Punkt entfernt ist. Wenn sich eine Person einem historischen Denkmal in einer Entfernung von 40 Metern nähert, wird der AR-Modus verfügbar. Gleichzeitig öffnet sich die Kamera und kurze Informationen zu den Objekten werden direkt in der Umgebung des Benutzers angezeigt. Letzterer kann mit virtuellen Objekten interagieren: Durch Berühren der Karte eines historischen Ortes können Sie das Album mit Bildern anzeigen.
Es scheint, dass die Anwendung sehr einfach ist, aber selbst hier gab es einige Fallstricke. Ich werde Sie nicht mit einer Geschichte über die Implementierung trivialer Dinge wie das Herunterladen von Daten von einem Server oder das Anzeigen von Punkten auf einer Karte langweilen. Ich werde direkt zu den Funktionen gehen, die die Probleme verursacht haben.
Problem 1. Gleitkommazahlen
Das erste, was zu tun war, war, Markierungspunkte im Raum entsprechend der tatsächlichen Position historischer Orte relativ zum aktuellen Standort und der Blickrichtung des Benutzers zu platzieren.
Zunächst haben wir uns für die bereits vorbereitete Bibliothek für iOS entschieden:
ARKit-CoreLocation . Das Projekt liegt auf GitHub im öffentlichen Bereich, enthält neben dem Code der Hauptklassen Integrationsbeispiele und ermöglicht es uns, die interessierte Aufgabe in ein paar Stunden zu erledigen. Die Bibliothek muss nur die Koordinaten der Punkte und das als Marker verwendete Bild eingeben.
Es überrascht nicht, dass diese Leichtigkeit bezahlt werden musste. Markierungspunkte schwebten ständig im Raum: Sie kletterten entweder an die Decke oder wurden irgendwo unter den Füßen gezeichnet. Nicht jeder Benutzer würde zustimmen, das AR-Objekt einige Minuten lang scharf zu stellen, um sich mit den Informationen vertraut zu machen, die ihn interessieren.
Wie sich herausstellte, waren viele mit diesem Bibliotheksfehler konfrontiert, aber eine Lösung wurde noch nicht gefunden. Der Code auf GitHub wurde leider seit mehr als sechs Monaten nicht mehr aktualisiert, daher musste ich ihn umgehen.
Wir haben versucht, die Höhe anstelle der festen Höhe in Koordinaten zu verwenden, die der LocationManager für die aktuelle Position des Benutzers zurückgegeben hat. Dies beseitigte das Problem jedoch nicht vollständig. Die vom Standortmanager stammenden Daten sprangen mit einer Ausbreitung von bis zu 60 Metern, sobald das Gerät in der Hand gedreht wurde. Infolgedessen war das Bild instabil, was uns natürlich nicht mehr zusagte.
Infolgedessen wurde beschlossen, die ARKit-CoreLocation-Bibliothek aufzugeben und Punkte selbst im Raum zu platzieren. Der Artikel ARKit und CoreLocation von Christopher Web-Orenstein hat dabei sehr geholfen. Ich musste etwas mehr Zeit aufwenden und einige mathematische Aspekte in meinem Gedächtnis auffrischen, aber das Ergebnis war es wert: AR-Objekte waren endlich an ihren Plätzen. Danach müssen sie nur noch entlang der Y-Achse gestreut werden, damit die Beschriftungen und Punkte besser lesbar sind, und eine Entsprechung zwischen dem Abstand von der aktuellen Position zum Punkt und der Z-Koordinate des AR-Objekts hergestellt werden, sodass Informationen über die nächsten historischen Orte im Vordergrund stehen.
Es war notwendig, die neue SCNNode-Position im Raum zu berechnen, wobei der Schwerpunkt auf den Koordinaten lag:
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)
Die folgenden Funktionen wurden der MatrixHelper-Klasse hinzugefügt:
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 } }
Zur Berechnung des Azimuts wurde die Erweiterung
CLLocationCoordinate2D hinzugefügt
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() } }
Problem 2. Überschüssige AR-Objekte
Das nächste Problem war eine große Anzahl von AR-Objekten. Es gibt viele historische Orte und Sehenswürdigkeiten in unserer Stadt, daher Würfel mit Informationen, die zusammengeführt und übereinander gekrochen werden. Es wäre für den Benutzer sehr schwierig, einen Teil der Inschriften zu erkennen, und dies könnte einen abstoßenden Eindruck hinterlassen. Nach der Besprechung haben wir beschlossen, die Anzahl der gleichzeitig angezeigten AR-Objekte zu begrenzen und nur Punkte in einem Radius von 500 Metern vom aktuellen Standort zu belassen.
In einigen Bereichen war die Punktekonzentration jedoch immer noch zu hoch. Um die Sichtbarkeit zu erhöhen, entschieden sie sich daher für Clustering. Auf dem Kartenbildschirm ist diese Funktion aufgrund der in MapKit eingebetteten Logik standardmäßig verfügbar. Im AR-Modus musste sie jedoch manuell implementiert werden.
Das Clustering basierte auf der Entfernung von der aktuellen Position zum Ziel. Wenn der Punkt in die Zone mit einem Radius fiel, der der halben Entfernung zwischen dem Benutzer und der vorherigen Attraktion aus der Liste entspricht, versteckte er sich einfach und war Teil des Clusters. Als sich der Benutzer näherte, nahm die Entfernung ab und der Radius der Clusterzone nahm entsprechend ab, sodass die in der Nähe befindlichen Sehenswürdigkeiten nicht zu Clustern verschmolzen. Um Cluster visuell von einzelnen Punkten zu unterscheiden, haben wir beschlossen, die Markierungsfarbe zu ändern und die Anzahl der Objekte in AR anstelle des Ortsnamens anzuzeigen.

Um die Interaktivität von AR-Objekten sicherzustellen, wurde ein UITapGestureRecognizer an ARSCNView angehängt und im Handler mithilfe der hitTest-Methode überprüft, auf welches der SCNNode-Objekte der Benutzer geklickt hat. Wenn sich herausstellte, dass es sich um ein Foto von nahe gelegenen Sehenswürdigkeiten handelt, öffnete die Anwendung das entsprechende Album im Vollbildmodus.
Problem 3. Radar
Während der Implementierung der Anwendung war es notwendig, die Punkte auf einem kleinen Radar anzuzeigen. Theoretisch sollte es keine Missverständnisse geben, da wir bereits den Azimut und die Entfernung zum Punkt berechnet und es sogar geschafft haben, sie in 3D-Koordinaten umzuwandeln. Es blieb nur, die Punkte im zweidimensionalen Raum auf dem Bildschirm zu platzieren.

Um das Rad nicht neu zu erfinden, wandten sie sich an die
Radar- Bibliothek, deren Open Source-Code auf GitHub veröffentlicht wurde. Die lebendigen Vorschauen und flexiblen Einstellungen des Beispiels waren ermutigend, aber in Wirklichkeit wurden die Punkte relativ zum tatsächlichen Ort im Raum verschoben. Nachdem wir einige Zeit damit verbracht hatten, die Formeln zu korrigieren, wandten wir uns der weniger schönen, aber zuverlässigeren Option zu, die im
iPhone Augmented Reality Toolkit beschrieben ist :
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
Es bleibt das Problem der Speicherung von Punkten und Fotos zu lösen. Für diese Zwecke wurde beschlossen, Contentful zu verwenden, und in der aktuellen Implementierung des Projekts passte er vollständig zu uns.


Zum Zeitpunkt der Entwicklung der mobilen Anwendung waren alle Token an kommerziellen Projekten beteiligt und durften mehrere Stunden lang inhaltlich Folgendes bereitstellen:
- mobiler Entwickler - praktisches Backend
- Content Manager - ein praktischer Administrationsbereich zum Ausfüllen von Daten
Die ähnliche Implementierung des Backends wurde ursprünglich von den Teams verwendet, die am Hackathon teilgenommen haben (wie am Anfang des Artikels erwähnt). Dies zeigt einmal mehr, dass Dinge wie Hackathons es Ihnen ermöglichen, Ihre dringenden Probleme bei Projekten zu lösen, etwas neu zu erstellen und auszuprobieren brandneu.
Fazit
Es war sehr interessant, eine
AR-Anwendung zu entwickeln. Dabei haben wir mehrere vorgefertigte Bibliotheken ausprobiert, aber wir mussten uns auch an die Mathematik erinnern und viele Dinge selbst schreiben.
Das Projekt war auf den ersten Blick einfach und erforderte viele Arbeitsstunden, um die Algorithmen zu implementieren und zu verfeinern, obwohl wir das Standard-SDK von Apple verwendet haben.
Wir haben die Anwendung kürzlich im
AppStore veröffentlicht . So sieht es bei der Arbeit aus.
Bisher gibt es in unserer Datenbank nur Punkte für Taganrog, jedoch kann jeder an der Erweiterung des „Versorgungsgebiets“ teilnehmen.