ARKit, SceneKit y Cómo controlar el mundo

En la Parte 1 de esta serie, pasamos por un flujo de trabajo donde procesamos un modelo 3D, creamos un proyecto AR en Xcode, comenzamos una sesión AR y colocamos nuestro modelo en nuestra escena aumentada.

En esta publicación, comenzaremos a poner nuestro modelo en acción, utilizando una variedad de métodos de SceneKit, y comenzaremos a interactuar con los objetos de nuestro mundo.

El proyecto para esta publicación se puede encontrar en https://github.com/AbovegroundDan/ARTutorial_Part2

Acciones de SceneKit

SceneKit proporciona un conjunto de acciones que se pueden aplicar a un nodo. Estas acciones se pueden utilizar para animar movimiento, rotación, escala y otras propiedades de nodo. Se pueden agrupar para ejecutarse al mismo tiempo, secuenciarse para ejecutarse una tras otra y repetirse o invertirse. La lista completa se puede encontrar en https://developer.apple.com/documentation/scenekit/scnaction

Continuaremos modificando el proyecto actual y comenzaremos a agregar algunas acciones a nuestro objeto. Comencemos agregando una rotación a nuestra esfera.

Después del método addSphere en nuestro HoverScene, agregue el siguiente método:

En este código, creamos una acción que describe una rotación de 180 grados alrededor del eje Y. Esta rotación debería tardar 5 segundos en completarse. Luego tomamos esta acción y la agregamos al nodo pasado. Ahora, necesitamos llamar a este método desde nuestro método addSphere. Al final del método, después de la línea donde agregamos el nodo secundario, agregue la siguiente línea:

Ahora, si ejecutamos este código, veremos que nuestra esfera gira para mirar hacia el otro lado y luego se detendrá. Lo que queremos hacer ahora es repetir esta animación, para que siga girando.

Cambiemos nuestro código addAnimation a lo siguiente:

Aquí, agregamos una acción de repetición para siempre, que toma la acción de rotación como entrada. Luego, en lugar de la acción de rotación, agregamos la acción repeatForever a nuestro nodo. Ejecute el código nuevamente y veremos que nuestra esfera ahora gira continuamente.

Agreguemos un poco de dinamismo a la esfera. También hagamos que se mueva un poco hacia arriba y hacia abajo. Vamos a agregar una acción de desplazamiento hacia arriba, una acción de desplazamiento hacia abajo, secuenciar esas dos acciones y luego agruparlas con nuestra acción de rotación existente. Así es como debería verse el método addAnimation:

Ahora tenemos una esfera giratoria y flotante que podemos colocar en cualquier parte de nuestro mundo.

HitTests y abstracciones

Agreguemos algo de interactividad a la escena. Establezcamos un objetivo para comenzar la escena haciendo que el usuario coloque las esferas en el mundo y luego toque para activarlas. Como nos estamos volviendo un poco más complejos con nuestro código, es hora de comenzar a abstraer la forma en que tratamos nuestros objetos de escena. Cree un nuevo grupo en nuestro proyecto llamado Objetos. En esta carpeta, crearemos un nuevo archivo Swift llamado SceneObject.swift.

Vamos a crear una clase base, SceneObject, que se deriva de SCNNode.

Queremos abstraer la carga del objeto del resto del código, así que creemos un método init () que tome un nombre de archivo. En este inicializador, moveremos el código que tenemos para cargar desde el archivo.

Ahora podemos crear una clase Esfera que se deriva de SceneObject:

Ahora tenemos una clase de objeto Esfera que puede cargarse sola. Dado que vamos a animar las esferas cuando las toquemos, eliminemos la llamada addAnimation del método addSphere en nuestro HoverScene. Además, dado que ahora hemos movido todo el código de carga a la clase Esfera, podemos inicializar Esfera y agregarlo directamente al nodo raíz de la escena. Nuestro método muy simplificado ahora se ve así:

Mucho más limpio!

Ahora, vamos a ver cómo podemos hacer una prueba de éxito. Ya tenemos un reconocedor de gestos de toque en nuestro controlador de vista, por lo que podemos conectarnos a eso, pero ¿cómo sabremos si nuestros toques golpean una Esfera, otro objeto o nada en absoluto?

Afortunadamente, nuestro ARSCNView puede ayudarnos con eso. Tiene el siguiente método:

Podemos alimentarlo con una ubicación en la vista, y nos devolverá una serie de nodos que están debajo de ese punto, sin importar cuál sea el valor z o la profundidad.

Como solo queremos agarrar objetos de Esfera, creemos un filtro rápido que verifique si cada nodo devuelto en hitTest es una Esfera. Para hacer eso, necesitamos tomar el nodo padre superior para cada nodo que queremos verificar. Volvamos a nuestro archivo Node + Extensions.swift y agreguemos el siguiente método:

Dado que todos los objetos en nuestra escena son hijos del nodo raíz de la escena, queremos detenernos cuando lleguemos a ese nodo y no verificar más. Dado que es un método recursivo, nos detenemos y regresamos cuando encontramos que el nodo raíz es el padre de nuestro nodo.

Con eso detrás, regresemos a nuestro ViewController y modifiquemos nuestro delegado de reconocimiento de gestos de toque. Queremos continuar agregando nuevas esferas si tocamos un espacio vacío, pero si tocamos una esfera existente, queremos comenzar su animación.

Cuando recibimos un toque, queremos obtener el punto en nuestra vista de escena, pasarlo al método hitTest de la vista de escena y ver qué recuperamos. Como solo queremos tratar con un objeto a la vez, tomamos el primero (si hay hits), luego usamos nuestra extensión topmost () para agarrar el padre superior y verificamos si es una esfera. Si es así, le agregamos nuestra animación. Si no obtuvimos ningún resultado de nuestra prueba, entonces lo hacemos como antes, agregando una nueva Esfera frente a la cámara.

Continúa y ejecuta la aplicación nuevamente. Podemos tocar para agregar una esfera, y luego tocar cualquier esfera para comenzar a moverla. Sin embargo, tenemos un error que presentamos. Podemos seguir tocando una esfera y las animaciones se seguirán agregando al objeto cada vez. Puede probarlo y ver que la esfera gira más rápido y se mueve hacia arriba y hacia abajo más rápido cada vez que la toca.

Dado que la animación es específica de la esfera, muevamos el código addAnimation a la Esfera misma, y ​​cambiemos el nombre a animar (). En lugar de node.addAnimation, podemos llamar a addAnimation. También agregaremos una bandera a la clase Esfera, que verificaremos antes de agregar la animación, y la configuraremos como verdadera la primera vez que se agregue:

Todo lo que queda por hacer es cambiar el código en nuestra devolución de llamada por gestos para ejecutar esta nueva llamada en la esfera misma.

Ya estamos listos con esta parte. Tenemos una esfera que podemos ubicar en el mundo, agregamos un poco de interacción para que podamos comenzar la animación y limpiamos un poco el código.

Crédito adicional

La forma en que colocamos una esfera en el mundo es bastante abrupta. Hacemos tapping y de repente está allí. Agreguemos un poco de dinamismo a esta función y animemos la esfera cuando la ubiquemos.

En nuestro método addSphere en HoverScene, agreguemos un efecto de escala. Cuando agreguemos la esfera, animaremos su escala y, en lugar de usar una escala lineal estándar, aplicaremos un efecto de rebote o pop-in.

Cambiemos nuestro método addSphere a lo siguiente, y agreguemos la función de temporización easeOutElastic, que nos proporcionará ese rebote:

Ahora, cuando tocamos para colocar una esfera, obtenemos un efecto animado bastante bueno.

Crédito extra, parte deux

Hemos estado haciendo muchas cosas con SceneKit, pero solo hemos rozado la superficie de algunas de las cosas que ARKit puede hacer. Como un avance rápido antes de llegar a más funcionalidad de ARKit, agreguemos un poco de diversión a la escena, haciendo que la esfera "mire" la cámara. Ya tenemos ganchos en el método updateAtTime del renderizador, y también tenemos una referencia a la cámara allí. Entonces, comencemos agregando un método a la clase Esfera, para que mire hacia una dirección particular. El "ojo" de la esfera ya se enfrenta a la Z negativa, que es la dirección hacia adelante del objeto. Lo que haremos es crear un método que tome un vector que marque un punto en el espacio al que se enfrentará nuestro "ojo".

En este código, observamos la distancia entre la esfera y la posición del objetivo (cámara). Si es inferior a una cantidad, animaremos el ojo para que se enfrente al objetivo. Si el ojo miraba hacia la cámara y el usuario se aleja más allá de esta distancia establecida, entraremos en nuestra animación de "patrulla". Una cosa a tener en cuenta en este código es que, dado que no hay una SCNAction útil para aplicar una animación "LookAt", ajustamos nuestra llamada look (at :) en una SCNTransaction, que nos permite animar el movimiento. Los motores de juegos 3D dedicados como Unity o Unreal tienen funciones de conveniencia para estas cosas, pero SceneKit aún no está en ese nivel.

Puede notar que hay una llamada a distancia en el SCNVector3 targetPos que se ha pasado, pero ese método no existe para SCNVector3. Lo que haremos es agregar una nueva extensión para esta llamada a distancia.

Puse ese código en un nuevo archivo UtilityExtensions.swift, pero siéntase libre de ponerlo donde quiera.

A continuación, tendremos que cambiar nuestro método updateAtTime para eliminar la marca de verificación y llamar a un método en nuestro controlador de escena cada fotograma. Nuestro controlador de escena se encargará de enviar el mensaje a todos los objetos de Esfera en nuestra escena.

En nuestro HoverScene, crearemos el método makeUpdateCameraPos, que filtrará solo los objetos de Esfera, y llamaremos al método de patrulla.

Cambiemos también nuestro método de ubicación para colocar las esferas un poco más lejos. Hagamos que el método didTapScreen coloque nuestra esfera a 5 metros de distancia en lugar de 1:

En nuestra clase Esfera, establezcamos nuestro umbral para activar la mirada a 4.85 metros:

Cambiemos también nuestra animación de Esfera para que se vea un poco y no se desplace.

Float.random es otra extensión que es simplemente:

Corre y coloca algunas esferas. Deberían animarse solos ahora, sin necesidad de tocarlos. Camina más cerca de cada uno y deberías verlos volver la vista hacia ti. Camine más lejos y deberían volver a patrullar su área. Ahora tenemos una escena de un futuro distópico, con ojos revoloteantes observando cada uno de nuestros movimientos.

¡Estén atentos para más diversión ARKit!