Cómo Yandex creó la realidad aumentada en Maps para iOS. Experiencia usando ARKit

Quedan menos personas que puedan sorprenderse con la Realidad Aumentada (RA). Para algunos, esta tecnología está asociada con un juguete durante un par de horas. Otros lo encuentran más práctico.


Mi nombre es Dmitry, y estoy desarrollando Yandex.Maps para iOS. Hoy les diré a los lectores de Habr cómo creamos el enrutamiento utilizando la realidad aumentada. También aprenderá sobre las características del uso del marco ARKit, gracias a lo cual la introducción de la realidad aumentada ya no es una preocupación de solo especialistas en el campo de la visión por computadora.



En 2009, la revista Esquire fue la primera de los medios en agregar soporte de realidad aumentada a su producto. En la portada de la revista se publicó un código con el que se podía ver a Robert Downey Jr. "en vivo".



El uso de AR en la industria del entretenimiento no se limitó a esto. Un vívido ejemplo fue el juego Pokemon Go, lanzado en 2016. Para julio de ese año, se descargó más de 16 millones de veces. El éxito del juego llevó a la aparición de numerosos clones con AR.


Los eventos importantes en la industria de AR en los últimos años pueden considerarse los anuncios de Google Glass y Microsoft Hololens. La aparición de tales dispositivos muestra el vector en el que se mueven las grandes empresas.


Apple no fue la excepción. En 2017, la compañía presentó el marco ARKit, cuya importancia para la industria difícilmente se puede sobreestimar. Y hablaremos de ello con más detalle.


ARKit


Características de ARKit, lo que facilita el uso de AR:


  • falta de necesidad de etiquetas especiales (marcadores),
  • integración con marcos de gráficos 2D / 3D de Apple existentes: SceneKit, SpriteKit, Metal,
  • alta precisión para determinar la posición y orientación del dispositivo en el espacio,
  • No es necesario calibrar la cámara o los sensores.

Bajo el capó de ARKit hay un sistema de odometría visual inercial que combina datos con los subsistemas visual (cámara) e inercial (acelerómetro, giroscopio) del dispositivo para determinar la posición y el desplazamiento en el escenario. El elemento de conexión de este sistema es el filtro de Kalman, un algoritmo que en cada momento selecciona la mejor de las lecturas de los dos subsistemas y nos la proporciona en forma de nuestra posición y orientación en el escenario. ARKit también tiene una "comprensión" de la escena: podemos definir superficies horizontales y verticales, así como las condiciones de iluminación de la escena. Por lo tanto, al agregar un objeto a la escena, podemos agregarle iluminación predeterminada, gracias a lo cual el objeto se verá más realista.


Por cierto

Pronto, se lanzará la versión 2.0 del marco, en la que se agregarán nuevas características y se mejorará significativamente la precisión de posicionamiento.


ARKit permitió a los desarrolladores integrar la realidad aumentada de alta calidad en sus aplicaciones mientras gastaban mucho menos esfuerzo. Lo demostraremos usando el ejemplo de Yandex.Maps.


Enrutamiento con AR en Yandex.Maps


Por lo general, después del anuncio de la nueva versión de iOS, muchos equipos de Yandex se reúnen para discutir la posibilidad de introducir nuevas funciones en sus aplicaciones. El equipo Yandex.Mart hizo lo mismo. Dentro de un mes desde el momento del anuncio de ARKit, a menudo discutimos cómo implementarlo en Maps. ¡Qué tipo de ideas no escuchamos el uno del otro! Rápidamente, llegamos a la conclusión de que una de las soluciones más útiles y superficiales es el uso de la realidad aumentada en el enrutamiento.


La elección de esta idea se debió al hecho de que muchos usuarios de tarjetas a menudo se encuentran con una situación en la que te encuentras en un área desconocida y necesitas decidir rápidamente a dónde ir. El enfoque estándar para el usuario promedio del mapa es abrir la aplicación, construir una ruta peatonal y, girando en su lugar, determinar dónde moverse. La idea de introducir la realidad aumentada en el enrutamiento de peatones es salvar al usuario de acciones innecesarias, mostrando de inmediato dónde debe moverse directamente sobre la imagen de la cámara.


Primero, quiero decir algunas palabras sobre el enrutamiento. ¿Qué pongo en este concepto? Desde el punto de vista de la implementación en una aplicación móvil, este es un conjunto bastante estándar de pasos que permiten al usuario ir del punto A al punto B:


  • selección de puntos de partida y llegada,
  • recibir una ruta en forma de un conjunto de puntos en coordenadas geográficas (latitud, longitud),
  • mostrar en el mapa de la línea de ruta,
  • acompañando al usuario con información adicional mientras se mueve a lo largo de la ruta.

No nos detendremos en los dos primeros puntos. Solo puedo decir que obtenemos la ruta a través de nuestra biblioteca multiplataforma Yandex.Mapkit, que también está disponible para usted en forma de pod. ¿Cómo difiere el enrutamiento de realidad aumentada del enrutamiento estándar en los mapas? En primer lugar, la principal diferencia es un mapa casi completamente oculto. El énfasis principal se pone en el área de la pantalla con la imagen de la transmisión de video de la cámara, en la que se superponen elementos visuales adicionales (marca de finalización, marca auxiliar e imagen de línea de ruta). Cada uno de estos elementos visuales tiene su propia carga semántica y su propia lógica (cuándo y cómo debe mostrarse). Consideraremos el papel de cada uno de estos elementos con más detalle más adelante, pero por ahora propongo considerar las tareas que teníamos antes:


  • aprender a colocar objetos en la escena ARKit, conociendo sus coordenadas geográficas,
  • aprenda a dibujar la interfaz de usuario necesaria en una escena 3D con un rendimiento suficiente.

Necesitábamos convertir las coordenadas de los puntos de geográficas a las coordenadas en el escenario, seleccionar qué puntos mostrar y mostrar toda la IU necesaria en la parte superior de la imagen de la cámara en la posición correcta. Pero todo resultó ser un poco más complicado de lo que parecía a primera vista.


Antes de comenzar a implementar las funciones directamente, uno de mis colegas recibió la tarea de hacer un prototipo que mostrara la posibilidad (o imposibilidad) de implementar una funcionalidad similar con un conjunto accesible de herramientas. Durante dos semanas, vimos a San Sanych arando los espacios abiertos del espacio abierto y los alrededores cercanos de nuestra oficina con un teléfono en la mano y mirando el mundo que nos rodea a través del prisma de la cámara. Como resultado, obtuvimos un prototipo funcional que mostraba cada punto de la ruta como una marca en el escenario con una distancia a la misma. Con la ayuda de este prototipo, fue posible, bajo una combinación exitosa de circunstancias, ir del trabajo al metro y casi nunca perderse. Pero en serio, confirmó la posibilidad de implementar la funcionalidad prevista. Pero quedaba una serie de tareas que nuestro equipo aún tenía que resolver.


Todo comenzó con el estudio de las herramientas. En ese momento, solo una persona en el equipo tenía experiencia trabajando con gráficos 3D. Echemos un vistazo rápido a las herramientas con las que tendrá que lidiar cualquiera que piense en implementar tales ideas con ARKit.


Herramientas y API


El trabajo principal de renderizar objetos es crear y administrar objetos de escena del framework SceneKit. Con el advenimiento de ARKit, la clase ARSCNView (el descendiente de la clase SCNView, la clase base para trabajar con la escena en SceneKit) se puso a disposición del desarrollador, que resuelve la mayoría de las tareas de integración de ARKit y SceneKit que requieren mucho tiempo, a saber:


  • sincronización de la posición del teléfono en el espacio con la posición de la cámara en el escenario,
  • el sistema de coordenadas de la escena coincide con el sistema de coordenadas ARKit,
  • Como fondo de la escena, se utiliza la transmisión de video de la cámara del dispositivo.

El objeto ARSCNView también proporciona al desarrollador un objeto de sesión de realidad aumentada que puede iniciarse con la configuración necesaria, detenerse o suscribirse a varios eventos utilizando el objeto delegado.


Para agregar objetos a la escena, se utilizan herederos o directamente objetos SCNNode. Esta clase representa una posición (vector tridimensional) en el sistema de coordenadas de su padre. Por lo tanto, obtenemos un árbol de objetos en la escena con una raíz en un objeto especial: el nodo raíz de nuestra escena. Todo aquí es muy similar a la jerarquía de objetos UIView en UIKit. Los objetos SCNNode se pueden mostrar en el escenario cuando agregan material e iluminación.


Para agregar realidad aumentada a una aplicación móvil, también debe conocer los objetos principales de la API de ARKit. El principal es el objeto de la sesión de realidad aumentada: ARSession. Este objeto lleva a cabo el procesamiento de datos y es responsable del ciclo de vida de la sesión de realidad aumentada. El propósito de este artículo no es volver a contar la documentación de ARKit y SceneKit, por lo que no escribiré sobre todos los parámetros de configuración disponibles de la sesión de realidad aumentada, sino que me centraré en uno de los parámetros más importantes de la configuración de la sesión de realidad aumentada para aplicaciones de navegación: worldAlignment. Este parámetro determina la dirección de los ejes de la escena en el momento de la inicialización de la sesión. En general, al inicializar una sesión de realidad aumentada, ARKit crea un sistema de coordenadas con un comienzo en un punto que coincide con la posición actual del teléfono en el espacio, y dirige el eje de este sistema según el valor de la propiedad woldAlignment. En nuestra implementación, se utiliza el valor gravityAndHeading, lo que implica que los ejes se direccionarán de la siguiente manera: el eje Y, en la dirección opuesta a la gravedad, el eje Z, hacia el sur, y el eje X, hacia el este.


alineación-mundo-gravedad-y-rumbo


Con una buena combinación de circunstancias, los ejes X / Z se alinearán con las direcciones hacia el sur / este, pero, debido a errores en las lecturas de la brújula, los ejes se pueden dirigir en un cierto ángulo a la dirección descrita en la documentación. Este es uno de los problemas con los que tuvimos que lidiar, pero más sobre eso más adelante.


Ahora que hemos examinado las herramientas básicas, resumamos: mapear una ruta usando SceneKit es agregar objetos SCNNode a la escena en las posiciones obtenidas mediante la conversión de coordenadas geográficas a coordenadas de escena. Antes de hablar sobre la conversión de coordenadas y, en general, sobre la colocación de objetos en la escena, hablemos sobre los problemas de renderizar elementos de la interfaz de usuario, suponiendo que sepamos la posición de los objetos en el escenario.


Marca final


El principal elemento visual del enrutamiento peatonal con realidad aumentada es la marca de llegada, que muestra el punto final de la ruta. También sobre la marca, le mostramos al usuario la distancia hasta el punto final de la ruta.


acabado-placemark-resumen


Tamaño


Cuando se nos mostró por primera vez el diseño de esta etiqueta, primero prestamos atención a los requisitos para el tamaño de esta etiqueta. No obedecieron las reglas de proyección en perspectiva. Explicaré que en los motores tridimensionales que se utilizan para crear, por ejemplo, juegos de computadora, el "aspecto" se modela utilizando la proyección en perspectiva. De acuerdo con las reglas de proyección en perspectiva, los objetos distantes se representan en una escala menor, y las líneas paralelas generalmente no son paralelas. Por lo tanto, el tamaño de proyección del objeto en el plano de la pantalla cambia linealmente (disminuye) a medida que la cámara se aleja del objeto en la escena. De la descripción de los diseños se deduce que el tamaño de la marca en la pantalla tiene un tamaño fijo (máximo) cuando se elimina menos de 50 m, luego disminuye linealmente de 50 ma 2 km, después de lo cual el tamaño mínimo permanece sin cambios. Tales requisitos obviamente se deben a la conveniencia del usuario. Permiten al usuario nunca perder el punto final de la ruta de la vista, por lo que el usuario siempre tendrá una idea de dónde moverse.


acabado-placemark-tamaño-demandas


Teníamos que entender cómo podríamos meternos en el mecanismo de proyección SceneKit que funcionaba de acuerdo con ciertas reglas. Quiero señalar de inmediato que teníamos alrededor de dos semanas para hacer todo sobre todo, por lo que simplemente no había tiempo para llevar a cabo un análisis en profundidad de varios enfoques para resolver los problemas planteados. Ahora, analizar nuestras decisiones, evaluarlas es mucho más simple, y podemos concluir que la mayoría de las decisiones tomadas fueron correctas. El requisito de tamaño, de hecho, fue el primer escollo. Todos los problemas descritos a continuación se pueden resolver utilizando SceneKit y UIKit. Traté de explicar en detalle cómo resolver cada uno de los problemas usando ambos enfoques. Qué enfoque usar depende de usted.


Imaginemos que decidimos implementar una etiqueta de acabado usando SceneKit. Si tenemos en cuenta que la etiqueta de acuerdo con los diseños debería haber parecido un círculo en la pantalla, entonces resulta obvio que en SceneKit el objeto de la etiqueta debería ser una esfera (ya que la proyección de la esfera en cualquier plano es un círculo). Para que la proyección tenga un cierto radio en la pantalla, definido en los requisitos de los diseñadores, es necesario conocer el radio de la esfera en cada momento del tiempo. Por lo tanto, al colocar una esfera de cierto radio en la escena en un determinado punto y actualizar constantemente su radio al acercarse o alejarse, obtendremos una proyección en la pantalla del tamaño requerido en cualquier momento. El algoritmo para determinar el radio de la esfera en un punto arbitrario en el tiempo es el siguiente:


  1. definir la posición del objeto en el escenario: el centro de la esfera,
  2. encuentre la proyección de este punto en el plano de la pantalla (usando la API de SceneKit),
  3. Para determinar el tamaño requerido de la marca en la pantalla, encontramos la distancia desde la cámara hasta el centro de la esfera en el escenario,
  4. determinamos el tamaño requerido en la pantalla por la distancia al objeto usando las reglas descritas en el diseño,
  5. sabiendo el tamaño de la marca en la pantalla (diámetro del círculo), elegimos cualquier punto en este círculo,
  6. hacer la proyección inversa (unprojectPoint) del punto seleccionado,
  7. Encontramos la longitud del vector desde el punto recibido en el escenario hasta el centro de la esfera.

El valor obtenido de la longitud del vector será el radio deseado de la esfera.


acabado-placemark-size-solution-scenekit


En el momento de la implementación, no pudimos encontrar una manera de determinar el tamaño del objeto en la escena, y decidimos dibujar la marca de meta usando UIKit. En este caso, el algoritmo repite los pasos 1-5, después de lo cual se dibuja un círculo del tamaño deseado en la pantalla con el centro en el punto obtenido en el paso 2 utilizando las herramientas UIKit. Un ejemplo de implementación de una etiqueta usando UIKit se puede encontrar aquí .


Algunas palabras sobre el código

Al final del artículo, proporcioné varios enlaces a materiales útiles y simplemente interesantes, incluidos ejemplos, en los que puede ver en detalle el código real que resuelve los problemas presentados en el artículo e implementa los algoritmos presentados. El principal interés en mi opinión es el prototipo de enrutamiento peatonal , que reúne toda la funcionalidad, con la excepción del mecanismo de ajuste del eje, que se describe en detalle a continuación.


El código anterior no pretende ser óptimo, completo y de calidad de producción =)


La diferencia entre usar SceneKit y UIKit en este caso también radica en el hecho de que al implementar en SceneKit, el objeto SCNNode para el punto final de la ruta (marca de finalización) se creará con material y geometría, ya que debe ser visible, mientras se usa UIKit necesitamos el objeto nodo exclusivamente para buscar la proyección en el plano de la pantalla (para determinar el centro de la marca en la pantalla). En este caso, no es necesario agregar geometría y material. Tenga en cuenta que la distancia desde la cámara al objeto SCNNode del punto final de la ruta se puede encontrar de dos maneras: utilizando las coordenadas geográficas de los puntos o como la longitud del vector entre los puntos en la escena. Esto es posible porque el objeto de la cámara es una propiedad SCNNode. Para obtener el nodo de la cámara, debe consultar la propiedad pointOfView de nuestra escena.


Aprendimos a determinar el radio del nodo de la marca de finalización en un punto arbitrario en el tiempo cuando se implementa en SceneKit y la posición de la vista de la marca de finalización si se implementa en UIKit. ¿Queda por entender cuándo es necesario actualizar estos valores? Este lugar es el método de objeto SCNSceneRendererDelegate:


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

Este método se llama después de cada cuadro de escena renderizado. Al actualizar los valores de propiedad en el cuerpo de este método, obtenemos una etiqueta de finalización que se muestra correctamente.


Animación


Después de que la marca de finalización apareciera en dev, procedimos a agregar animación ondulada a esta marca. Creo que para la mayoría de los desarrolladores de iOS crear animaciones no es gran cosa. Pero al pensar en el método de implementación, nos encontramos con el problema de actualizar constantemente el marco de nuestra vista. Tenga en cuenta que en la mayoría de los casos, las animaciones se agregan a los objetos estáticos de UIView. Un problema similar: una actualización constante del radio de la geometría del nodo surge cuando se implementa usando SceneKit. El hecho es que la animación pulsante se reduce a la animación del tamaño del círculo (para UIKit) y el radio de la esfera (para SceneKit). Sí, sí, sabemos que en UIKit este tipo de animación se puede hacer usando CALayer, pero para simplificar la narración, decidí considerar este tema simétricamente para los dos marcos. Considere una implementación en UIKit. Si agrega código que anima el mismo cuadro al código existente que actualiza el cuadro de vista, la animación se interrumpirá al establecer explícitamente el cuadro. Por lo tanto, como solución a este problema, decidimos usar la animación de la propiedad transform.scale.xy del objeto UIView. Al implementar usando SceneKit, tendrá que agregar animación de la propiedad de escala al objeto SCNNode. Lo bueno de usar SceneKit en este caso es el hecho de que es totalmente compatible con CoreAnimation, por lo que no es necesario aprender una nueva API. El código que implementa una animación similar a la animación de etiquetas en Yandex.Maps se parece a esto:


 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") 

Cartelera


Al comienzo del artículo, mencioné una valla publicitaria con una distancia al punto final de la ruta, que, en esencia, es una etiqueta con texto siempre ubicado sobre la marca de meta. Por tradición, describiré los problemas inherentes a las implementaciones en UIKit y SceneKit, explicando las posibles soluciones para cada uno de los marcos.


Comencemos con UIKit. En este caso, la cartelera es un UILabel normal, en el que el texto se actualiza constantemente y muestra la distancia hasta el punto final de la ruta. Veamos el problema al que nos enfrentamos.


acabado-placemark-billboard-problem-uikit


Si configura una etiqueta en un marco y luego gira el teléfono, veremos que el marco no cambia (sería extraño si no fuera así). Al mismo tiempo, nos gustaría que la etiqueta permanezca paralela al plano de la tierra.


acabado-placemark-billboard-deseado-uikit


Creo que todos entienden que al cambiar la orientación del dispositivo, debemos girar la etiqueta, pero ¿en qué ángulo? Si enciende la imaginación e imagina mentalmente todos los ejes de los sistemas de coordenadas y los vectores involucrados en este proceso, podemos concluir que el ángulo de rotación es igual al ángulo entre el eje x del sistema de coordenadas UIKit y la proyección del eje X del sistema de coordenadas SceneKit en el plano de la pantalla.


acabado-placemark-billboard-solution-uikit


Una tarea simple que una vez más demostró la utilidad del curso de geometría escolar.


Cuando implemente la marca de acabado con SceneKit, lo más probable es que necesite renderizar la cartelera con distancia usando las herramientas de SceneKit, lo que significa que definitivamente tendrá la tarea de hacer que el objeto SCNNode esté siempre orientado hacia la cámara. Creo que el problema se volverá más claro si miras la imagen:


acabado-placemark-billboard-problem-scenekit


Este problema se resuelve utilizando la API SCNBillboardConstraint. Al agregar una constante con un eje Y libre a la colección de restricciones de nuestro nodo, obtenemos un nodo que gira alrededor del eje Y de su sistema de coordenadas, para estar siempre orientado hacia la cámara. La única tarea del desarrollador es colocar este nodo a la altura correcta para que la cartelera con la distancia sea siempre visible para el usuario.


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

Asistente de etiqueta


Una de las características principales del enrutamiento peatonal con realidad aumentada, dentro del equipo, consideramos una etiqueta auxiliar: un elemento visual especial que aparece en la pantalla en el momento en que el punto final de la ruta abandona la zona de visibilidad y muestra al usuario dónde girar el teléfono para que la etiqueta aparezca en la pantalla línea de meta


finish-placemark-hint-overview


Estoy seguro de que muchos de los lectores han encontrado funcionalidades similares en algunos juegos, la mayoría de las veces tiradores. Qué sorpresa fue nuestro equipo cuando vimos este elemento de interfaz de usuario en los diseños. Debo decir de inmediato que la implementación correcta de una función de este tipo puede requerir más de una hora de experimentación por parte de usted, pero el resultado final vale la pena. Comenzamos definiendo requisitos, a saber:


  • para cualquier orientación del dispositivo, la etiqueta se mueve a lo largo de los bordes de la pantalla,
  • Si el usuario ha girado 180 grados hasta el punto final de la ruta, la etiqueta se muestra en la parte inferior de la pantalla,
  • en cada momento, girar hacia la marca debería ser el giro más corto hasta el punto final de la ruta.

Después de describir los requisitos, comenzamos la implementación. Casi de inmediato, llegamos a la conclusión de que el renderizado se haría con UIKit. El principal problema con la implementación fue la determinación del centro de esta etiqueta en cada punto de tiempo. Después de considerar la marca final, dicha tarea no debería causar dificultades, por lo que no me detendré en su solución en detalle. En el artículo solo daré una descripción del algoritmo para elegir el centro de la etiqueta auxiliar, y el código fuente se puede encontrar aquí .


Algoritmo del centro de búsqueda Algoritmo de búsqueda:


  1. cree un objeto SCNNode para el punto final de la ruta con una posición en la escena obtenida de la coordenada geográfica del punto,
  2. encuentra la proyección de un punto en el plano de la pantalla,
  3. encuentre la intersección del segmento desde el centro de la pantalla hasta el punto de la proyección encontrada con los segmentos de los límites de la pantalla en el sistema de coordenadas de la pantalla.

acabado-placemark-sugerencia-solución


El punto de intersección encontrado es el centro deseado de la marca auxiliar. Por analogía con el código que actualiza los parámetros de la etiqueta de finalización, colocamos el código que representa la etiqueta auxiliar en el método delegado que ya se mencionó anteriormente.


Polilínea de ruta


Después de haber construido una ruta y haber visto la marca de llegada en la pantalla, el usuario puede alcanzarla orientando solo en la dirección de la marca, pero la ruta se llama así porque muestra la ruta al usuario. Pensamos que sería muy extraño reducir la funcionalidad del enrutamiento peatonal, excluyendo la visualización de ruta de la versión AR. Para visualizar la línea de ruta, se decidió mostrar un conjunto de flechas moviéndose a lo largo de ella. En este caso, los diseñadores estaban satisfechos de que las flechas prácticamente desaparecerían al alejarse (el tamaño estaría determinado por las reglas de proyección en perspectiva), y se decidió usar SceneKit para la implementación.


Antes de proceder a describir la implementación, es importante tener en cuenta que, por diseño, las flechas deberían haber estado a una distancia de 3 m entre sí. Si estima el número de objetos (flechas) que deben representarse con una ruta de aproximadamente 1 km de largo, entonces serán aproximadamente 330 piezas. Al mismo tiempo, a cada objeto se le agrega una animación de movimiento a lo largo de su parte de la ruta. Tenga en cuenta que las flechas alejadas de la posición de la cámara en el escenario a una distancia de aproximadamente 100-150 metros son prácticamente invisibles debido a su pequeño tamaño. Habiendo considerado estos factores, se decidió no mostrar todos los objetos, sino mostrar solo aquellos que se eliminan del usuario a no más de 100 metros a lo largo de la línea de ruta, actualizando periódicamente el conjunto de objetos visualizados. Mostramos una cantidad suficiente de información visual, eliminando cálculos innecesarios de SceneKit y ahorrando la batería del usuario.


ruta-polilínea-resumen


Veamos los pasos principales que tuvimos que realizar para obtener el resultado final:


  • selección de la sección de ruta para la que mostraremos las primitivas,
  • creación de modelos 3D,
  • creación de animación
  • actualizar al conducir por una ruta.

Seleccionar una parcela para mostrar


Como señalé anteriormente, no mostramos flechas para toda la ruta, sino que seleccionamos la sección óptima para mostrar. La elección de un segmento en un punto arbitrario en el tiempo consiste en encontrar el segmento de ruta más cercano (la ruta es una secuencia de segmentos / segmentos) a la posición actual del usuario y seleccionar segmentos desde el punto más cercano al punto final de la ruta hasta que su longitud total exceda los 100 metros.


ruta-polilínea-ruta-selección-parte


Creación de modelos 3D


Consideremos con más detalle el proceso de creación de un modelo 3D. En la mayoría de los casos, todo lo que necesita hacer para crear un modelo 3D simple (como nuestra flecha) es abrir cualquier editor 3D, dedicar un tiempo a dominarlo y crear este modelo en él. En caso de que los muchachos de su equipo tengan experiencia en modelado 3D, o tengan tiempo para aprender, por ejemplo, 3DMax (y debe comprarse), entonces es increíblemente afortunado. Desafortunadamente, en el momento de la implementación de esta función, ninguno de nosotros tenía experiencia especial, no había tiempo libre para la capacitación, por lo que tuvimos que hacer un modelo, por así decirlo, con medios improvisados. Me refiero a la descripción del modelo en el código. Todo comenzó con la presentación de un modelo 3D en forma de triángulos. Luego tuvimos que encontrar manualmente las coordenadas de los vértices de estos triángulos en el sistema de coordenadas del modelo, y luego crear una matriz de índices de los vértices de los triángulos. Con estos datos a nuestra disposición, podemos crear la geometría necesaria directamente en SceneKit. Puede crear un modelo similar al nuestro, por ejemplo, así:


 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) } 

El resultado final se ve así:


ruta-polilínea-flecha-modelo


Animación de línea de ruta


El siguiente paso en el camino para mostrar una línea animada de la ruta fue la etapa de creación de la animación en sí. Pero, ¿cuál es la forma de realizar la animación, que en la forma final parece que la flecha comienza su movimiento en el punto inicial de la sección seleccionada de la ruta y "flota" a lo largo de la ruta hasta el final de esta sección?



No describiré todas las formas posibles de crear tal animación, sino que me detendré en más detalles sobre el método que hemos elegido. Después de seleccionar una sección de la ruta, la dividimos en secciones de la misma longitud: secciones de la animación de una flecha. Cada sección de este tipo está resaltada en color y tiene una longitud igual a la distancia entre las flechas.


ruta-polilínea-ruta-partición-partición


Al comienzo de cada sección, creamos el objeto SCNNode de la flecha, cuya animación consiste en moverse a lo largo de su sección.


ruta-polilínea-flechas-posición-inicial


Como puede ver, la sección de animación a veces consta de un segmento, a veces de dos o más. Todo depende del paso (en nuestro caso, 3 metros) entre las flechas y las coordenadas de los puntos que componen la ruta.


Una animación de flecha es una secuencia de dos pasos:


  • Apariencia en la posición inicial con el ángulo de rotación inicial,
  • Una secuencia de desplazamientos a lo largo de segmentos con rotaciones en los puntos de conexión de los segmentos.

Esquemáticamente, se ve así:


ruta-polilínea-flecha-anitaion-pasos


Nos pareció la forma más fácil de implementar una animación de este tipo utilizando la API SCNAction, una API declarativa que le permite crear convenientemente animaciones secuenciales, grupales y repetitivas. Puede ver la implementación con más detalle aquí . Debido al hecho de que cada flecha termina su animación en el punto de inicio de la sección de animación de la siguiente flecha, se crea la impresión de movimiento continuo de la flecha a lo largo de toda la sección seleccionada de la ruta.


Sobre esto, propongo completar la consideración de varios aspectos de la representación y pasar a la parte principal: determinar las posiciones de los objetos en el escenario mediante las coordenadas geográficas de los objetos.


Determinar la posición de un objeto en la escena.


Comenzamos la conversación sobre la determinación de la posición de un objeto en la escena considerando los sistemas de coordenadas, cuya conversión debe llevarse a cabo. Solo hay 2 de ellos:


  • coordenadas geodésicas (o geográficas para simplificar): la posición de los objetos (puntos de ruta) en el mundo real,
  • Coordenadas cartesianas: la posición de los objetos en la escena (en ARKit). Recuerde que el sistema de coordenadas de la escena coincide con el sistema de coordenadas ARKit (en el caso de utilizar ARSCNView).

La traducción de un sistema de coordenadas a otro y viceversa es posible debido al hecho de que las coordenadas en ARKit se miden en metros, y el desplazamiento entre dos coordenadas geodésicas se puede traducir con gran precisión en el desplazamiento en metros a lo largo de los ejes X y Z del sistema de coordenadas ARKit en pequeñas compensaciones. Permítame recordarle que las coordenadas geodésicas son puntos con cierta longitud y latitud.


Recordemos conceptos tan importantes del curso de la geografía como paralelos y meridianos, y sus propiedades básicas:


  • Paralelo es una línea con un valor de grado de latitud. Las longitudes de los diversos paralelos son diferentes.
  • Meridiano : una línea con un valor de grado de longitud. Las longitudes de todos los meridianos son iguales.

Ahora veamos cómo puedes calcular el desplazamiento en metros, entre dos coordenadas geodésicas con coordenadas. \ en línea (lat_1, lon_1) y \ en línea (lat_2, lon_2) :


\ Delta x = \ Delta lon \ times metersInLonDegree (lat_ {0}) , \ Delta z = \ Delta lat \ times metersInLatDegree


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


Explicación

El desplazamiento en coordenadas geodésicas se asigna linealmente a metros solo en pequeños desplazamientos. En grandes desplazamientos, es necesario tomar honestamente la integral.


Ahora que somos capaces de traducir el desplazamiento de un sistema de coordenadas a otro, necesitamos decidir sobre un punto de referencia, un punto para el cual las coordenadas geográficas y las coordenadas en ARKit (coordenadas en el escenario) son conocidas al mismo tiempo. Una vez encontrado ese punto, podemos determinar la coordenada de cualquier objeto en el escenario, conociendo su coordenada geográfica y utilizando las fórmulas anteriores.


Para mayor claridad, considere un ejemplo:
Al comienzo de la sesión de realidad aumentada, le pedimos a CoreLocation nuestra coordenada geográfica y la recibimos al instante: \ en línea (lat_0, lon_0) . Recordando el hecho de que el origen del sistema de coordenadas ARKit está al comienzo de la sesión en el punto donde se encuentra el dispositivo, obtuvimos el punto de referencia, ya que conocemos la coordenada geográfica y la coordenada en el escenario \ inline (x_0, y_0, z_0) = (0,0,0) . Necesitamos encontrar la coordenada en la escena del objeto con una coordenada geográfica \ en línea (lat_1, lon_1) . Para hacer esto, encuentre el desplazamiento en metros entre la coordenada geográfica del objeto y la coordenada geográfica de nuestro punto de referencia, y luego agregue el desplazamiento encontrado a la coordenada en la escena del punto de referencia. La coordenada resultante en la escena será la deseada.


coordenadas-conversión-objeto-posición-en-escena


Observo que la posición en la escena encontrada de esta manera corresponderá a la posición del objeto en el mundo real solo si el eje X / Z del sistema de coordenadas de la escena está alineado con las direcciones hacia el Sur / Este. La alineación del eje, en teoría, debe lograrse configurando el indicador worldAlignment en gravitiAndHeading. Pero como dije al comienzo de la publicación, esto está lejos de ser siempre el caso.


Consideremos con más detalle el método para determinar el punto de referencia. Para hacer esto, presentamos el concepto de estimación : un conjunto de coordenadas geográficas y coordenadas en el escenario.


coordenadas-conversión-estadísticas-definición


El método propuesto anteriormente para determinar el punto de referencia no siempre se puede utilizar. En el momento del inicio de una sesión de realidad aumentada, una solicitud de CLLocation de un usuario puede no ejecutarse inmediatamente, además, la precisión de la coordenada obtenida puede tener un gran error. Sería más correcto pedirle a SceneKit una posición en el escenario en el momento en que obtengamos el valor de CoreLocation. En este caso, los componentes de la estimación resultante se obtienen al mismo tiempo, y tenemos la oportunidad de utilizar cualquiera de las estimaciones como punto de referencia. Cuando se trabaja con ARKit, el error de compensación se acumula con el tiempo, por lo que Apple no recomienda usar ARKit como herramienta de navegación.


Cuando decidimos implementar el enrutamiento peatonal con realidad aumentada, investigamos un poco las soluciones que existían en ese momento, utilizando ARKit para tareas similares, y encontramos el marco ARKit + CoreLocation. La idea de este marco era que gracias a ARKit podemos determinar con mayor precisión la ubicación del usuario que cuando se usa exclusivamente CoreLocation.


Concepto ARKit + CoreLocation:


  • al recibir CLLocation de CLLocationManager
    • solicitar una posición en la escena usando scene.pointOfView.worldPosition
    • guarde este par de coordenadas (estimación) en el búfer
  • obtener la ubicación exacta si es necesario
    • elige la mejor estimación
    • calcular el desplazamiento entre la posición actual en el escenario y la posición en el escenario de la mejor estimación

, , CoreLocation, .


, « ». , .


(, ):


  • ( horizontalAccuracy),
  • ,
  • 100 .

CoreLocation . , , CoreLocation , 100 .


, . , , ( 100 ).



, X/Z ARKit / . ARKit , , .


Por qué

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


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



, . , \ en línea t_1 CLLocationManager \ en línea (lat_1, lon_1)\ en línea (x_1, z_1) . \ en línea t_2 CLLocationManager — \ en línea (lat_2, lon_2) \ en línea (x_2, z_2) en consecuencia


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


coordenadas-conversión-corrección-ángulo-problema


ARKit Y? . :


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

. . CLLocationManager' , ( ), ( ).


?

. , , . , , GPS .


1, 2 : \ inline initialBearing (1,2) y \ inline initialBearing (1,2_ {calc}) , \ en línea 2_ {calc} – 2, ARKit. \ inline initialBearing (a, b) ( Bearing).


coordenadas-conversión-corrección-ángulo-cálculo-por-par


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


coordenadas-conversión-corrección-ángulo-error-cálculo


, , .


. , . Por ejemplo:


  • N ,
  • ,
  • M ( ?).

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


, . , , ( , , ).


Prueba


, . , , , . 2 :


  • ,
  • .

- , , , , .


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


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


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


, ARKit.


corrección-ángulo-cálculo-alg-testing-street-before-corrección


, .


corrección-ángulo-cálculo-alg-testing-street-after-corrección


( 3-4 ) , .


corrección-ángulo-cálculo-alg-testing-street-after-last-correct


JS, AR CoreLocation.


corrección-ángulo-cálculo-alg-testing-tracks


— gravity worldAlignment . , . .


En lugar de una conclusión


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


Enlaces utiles



, . .

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


All Articles