Como o Yandex criou a realidade aumentada no Maps para iOS. Experiência usando o ARKit

Restam menos pessoas que podem se surpreender com a Realidade Aumentada (AR). Para alguns, essa tecnologia está associada a um brinquedo por algumas horas. Outros acham mais prático.


Meu nome é Dmitry e estou desenvolvendo Yandex.Maps para iOS. Hoje vou contar aos leitores da Habr como criamos o roteamento usando realidade aumentada. Você também aprenderá sobre os recursos do uso da estrutura ARKit, graças aos quais a introdução da realidade aumentada não é mais a preocupação de apenas especialistas no campo da visão computacional.



Em 2009, a revista Esquire foi a primeira entre a mídia a adicionar suporte de realidade aumentada ao seu produto. Na capa da revista postou um código com o qual você podia ver Robert Downey Jr. "ao vivo".



O uso de RA na indústria do entretenimento não se limitou a isso. Um exemplo vívido foi o jogo Pokemon Go, lançado em 2016. Em julho daquele ano, ele foi baixado mais de 16 milhões de vezes. O sucesso do jogo levou ao surgimento de numerosos clones com AR.


Eventos significativos na indústria de AR nos últimos anos podem ser considerados os anúncios do Google Glass e Microsoft Hololens. A aparência desses dispositivos mostra o vetor no qual as grandes empresas se deslocam.


A Apple não foi exceção. Em 2017, a empresa introduziu a estrutura ARKit, cuja importância para a indústria dificilmente pode ser superestimada. E falaremos sobre isso em mais detalhes.


ARKit


Recursos do ARKit, facilitando o uso do AR:


  • falta de tags especiais (marcadores),
  • integração com as estruturas gráficas 2D / 3D da Apple existentes - SceneKit, SpriteKit, Metal,
  • alta precisão na determinação da posição e orientação do dispositivo no espaço,
  • não é necessário calibrar a câmera ou os sensores.

Sob o capô do ARKit, existe um sistema de odometria inercial visual que combina dados com os subsistemas visual (câmera) e inercial (acelerômetro, giroscópio) do dispositivo para determinar a posição e o deslocamento no palco. O elemento de conexão desse sistema é o filtro Kalman - um algoritmo que, a cada momento, seleciona as melhores leituras dos dois subsistemas e as fornece na forma de nossa posição e orientação no palco. O ARKit também tem um "entendimento" da cena - podemos definir superfícies horizontais e verticais, bem como as condições de iluminação da cena. Assim, ao adicionar um objeto à cena, podemos adicionar iluminação padrão, graças à qual o objeto parecerá mais realista.


By the way

Em breve, a versão 2.0 do framework será lançada, na qual novos recursos serão adicionados e a precisão do posicionamento será significativamente aprimorada.


O ARKit permitiu que os desenvolvedores incorporassem realidade aumentada de alta qualidade em seus aplicativos, gastando muito menos esforço. Vamos demonstrar isso usando o exemplo de Yandex.Maps.


Roteamento com AR no Yandex.Maps


Normalmente, após o anúncio da nova versão do iOS, muitas equipes da Yandex se reúnem para discutir a possibilidade de introduzir novos recursos em seus aplicativos. A equipe do Yandex.Mart fez o mesmo. Dentro de um mês a partir do momento do anúncio do ARKit, discutimos frequentemente como implementá-lo no Maps. Que tipo de idéias não ouvimos um do outro! Rapidamente, chegamos à conclusão de que uma das soluções mais úteis e de superfície é o uso de realidade aumentada no roteamento.


A escolha dessa idéia se deve ao fato de muitos usuários de cartões frequentemente encontrarem uma situação em que você se encontra em uma área desconhecida e precisa decidir rapidamente para onde ir. A abordagem padrão para o usuário médio do mapa é abrir o aplicativo, criar uma rota para pedestres e, no lugar, determinar para onde se mover. A idéia de introduzir a realidade aumentada no roteamento de pedestres é salvar o usuário de ações desnecessárias, mostrando imediatamente onde você precisa se mover diretamente sobre a imagem da câmera.


Primeiro, quero dizer algumas palavras sobre roteamento. O que eu coloco nesse conceito? Do ponto de vista da implementação em um aplicativo móvel, este é um conjunto bastante padrão de etapas que permitem ao usuário ir do ponto A ao ponto B:


  • seleção de pontos de partida e chegada,
  • receber uma rota sob a forma de um conjunto de pontos em coordenadas geográficas (latitude, longitude),
  • exibido no mapa da linha de rota,
  • acompanhar o usuário com informações adicionais enquanto se move ao longo da rota.

Não vamos nos concentrar nos dois primeiros pontos. Só posso dizer que obtemos a rota por meio da biblioteca de plataforma cruzada Yandex.Mapkit, que também está disponível para você na forma de um pod. Como o roteamento de realidade aumentada difere do roteamento padrão nos mapas? Primeiro de tudo, a principal diferença é um mapa quase completamente oculto. A ênfase principal é colocada na área da tela com a imagem do fluxo de vídeo da câmera, na qual elementos visuais adicionais são sobrepostos (marca de chegada, marca auxiliar e imagem da linha de rota). Cada um desses elementos visuais possui sua própria carga semântica e sua própria lógica (quando e como deve ser exibida). Consideraremos o papel de cada um desses elementos em mais detalhes posteriormente, mas por enquanto proponho considerar as tarefas que tínhamos diante de nós:


  • aprender a posicionar objetos na cena do ARKit, conhecendo suas coordenadas geográficas,
  • Aprenda a desenhar a interface do usuário necessária em uma cena 3D com desempenho suficiente.

Precisávamos converter as coordenadas dos pontos de geográficas para as coordenadas no palco, selecionar os pontos a serem exibidos e exibir toda a interface do usuário necessária na parte superior da imagem da câmera na posição correta. Mas tudo acabou sendo um pouco mais complicado do que parecia à primeira vista.


Antes de começar a implementar os recursos diretamente, um de meus colegas recebeu a tarefa de criar um protótipo mostrando a possibilidade (ou impossibilidade) de implementar funcionalidades semelhantes com um conjunto acessível de ferramentas. Durante duas semanas, assistimos a San Sanych lavrando os espaços abertos e os arredores próximos de nosso escritório com um telefone na mão e olhando o mundo ao nosso redor através do prisma da câmera. Como resultado, obtivemos um protótipo funcional que mostrava cada ponto do percurso como uma marca no palco, com uma distância a ele. Com a ajuda desse protótipo, foi possível, sob uma combinação bem-sucedida de circunstâncias, ir do trabalho ao metrô e quase nunca se perder. Mas, falando sério, ele confirmou a possibilidade de implementar a funcionalidade pretendida. Mas ainda havia várias tarefas que nossa equipe ainda precisava resolver.


Tudo começou com o estudo de ferramentas. Naquela época, apenas uma pessoa da equipe tinha experiência em trabalhar com gráficos 3D. Vamos dar uma olhada rápida nas ferramentas com as quais qualquer um que pensa em implementar essas idéias com o ARKit.


Ferramentas e APIs


A principal tarefa de renderizar objetos é criar e gerenciar objetos de cena da estrutura do SceneKit. Com o advento do ARKit, a classe ARSCNView (o descendente da classe SCNView - a classe base para trabalhar com a cena no SceneKit) ficou disponível para o desenvolvedor, que resolve a maioria das tarefas demoradas de integração do ARKit e SceneKit, a saber:


  • sincronização da posição do telefone no espaço com a posição da câmera no palco,
  • o sistema de coordenadas da cena coincide com o sistema de coordenadas ARKit,
  • como pano de fundo da cena, o fluxo de vídeo da câmera do dispositivo é usado.

O objeto ARSCNView também fornece ao desenvolvedor um objeto de sessão de realidade aumentada que pode ser iniciado com a configuração necessária, interrompida ou inscrita em vários eventos usando o objeto delegado.


Para adicionar objetos à cena, herdeiros ou objetos diretamente do SCNNode são usados. Esta classe representa uma posição (vetor tridimensional) no sistema de coordenadas de seu pai. Assim, obtemos uma árvore de objetos em cena com uma raiz em um objeto especial - o rootNode da nossa cena. Tudo aqui é muito semelhante à hierarquia dos objetos UIView no UIKit. Os objetos SCNNode podem ser exibidos no palco quando adicionam material e iluminação.


Para adicionar realidade aumentada a um aplicativo móvel, você também precisa conhecer os principais objetos da API do ARKit. O principal é o objeto da sessão de realidade aumentada - ARSession. Este objeto realiza o processamento de dados e é responsável pelo ciclo de vida da sessão de realidade aumentada. O objetivo deste artigo não é recontar a documentação do ARKit e SceneKit, portanto, não escreverei sobre todos os parâmetros de configuração disponíveis da sessão de realidade aumentada, mas focarei em um dos parâmetros mais importantes da configuração da sessão de realidade aumentada para aplicativos de navegação - worldAlignment. Este parâmetro determina a direção dos eixos da cena no momento da inicialização da sessão. Em geral, ao inicializar uma sessão de realidade aumentada, o ARKit cria um sistema de coordenadas com um início que coincide com a posição atual do telefone no espaço e direciona o eixo desse sistema, dependendo do valor da propriedade woldAlignment. Em nossa implementação, é usado o valor gravityAndHeading, o que implica que os eixos serão direcionados da seguinte forma: o eixo Y - na direção oposta à gravidade, o eixo Z - ao sul e o eixo X - ao leste.


mundo-alinhamento-gravidade-e-rumo


Com uma boa combinação de circunstâncias, os eixos X / Z estarão de fato alinhados com as direções para o sul / leste, mas, devido a erros nas leituras da bússola, os eixos podem ser direcionados em um determinado ângulo em relação à direção descrita na documentação. Esse é um dos problemas com os quais tivemos que lidar, mas mais sobre isso mais tarde.


Agora que examinamos as ferramentas básicas, vamos resumir: mapear uma rota usando o SceneKit está adicionando objetos SCNNode à cena nas posições obtidas pela conversão de coordenadas geográficas em coordenadas de cena. Antes de falarmos sobre a conversão de coordenadas e, geralmente, sobre a colocação de objetos em cena, vamos falar sobre os problemas de renderização de elementos da interface do usuário, assumindo que sabemos a posição dos objetos no palco.


Marca de chegada


O principal elemento visual do roteamento de pedestres com realidade aumentada é a marca de chegada, que exibe o ponto final da rota. Também acima da marca, mostramos ao usuário a distância até o ponto final da rota.


visão geral do marcador de acabamento


Tamanho


Quando nos foi mostrado o design dessa tag, prestamos atenção aos requisitos para o tamanho dessa tag. Eles não obedeceram às regras da projeção em perspectiva. Explicarei que nos mecanismos tridimensionais usados ​​para criar, por exemplo, jogos de computador, o “visual” é modelado usando projeção em perspectiva. De acordo com as regras de projeção em perspectiva, objetos distantes são representados em uma escala menor e linhas paralelas geralmente não são paralelas. Assim, o tamanho da projeção do objeto no plano da tela muda linearmente (diminui) à medida que a câmera se afasta do objeto na cena. Resulta da descrição dos layouts que o tamanho da marca na tela tem um tamanho fixo (máximo) quando removido a menos de 50 m, depois diminui linearmente de 50 ma 2 km, após o que o tamanho mínimo permanece inalterado. Tais requisitos são obviamente devidos à conveniência do usuário. Eles permitem que o usuário nunca perca o ponto final da rota da visualização, para que ele sempre tenha uma idéia de para onde se mover.


acabamento-marcador-tamanho-demandas


Tínhamos que entender como poderíamos nos inserir no mecanismo de projeção SceneKit que funcionava de acordo com certas regras. Quero notar imediatamente que tínhamos cerca de duas semanas para fazer tudo sobre tudo, portanto simplesmente não havia tempo para realizar uma análise aprofundada de várias abordagens para resolver os problemas colocados. Agora, analisar nossas decisões, avaliá-las é muito mais simples, e podemos concluir que a maioria das decisões tomadas estava correta. A exigência de tamanho, de fato, foi o primeiro obstáculo. Todos os problemas descritos abaixo podem ser resolvidos usando o SceneKit e o UIKit. Tentei explicar em detalhes como resolver cada um dos problemas usando as duas abordagens. Qual abordagem usar depende de você.


Vamos imaginar que decidimos implementar uma etiqueta de acabamento usando o SceneKit. Se levarmos em conta que o rótulo de acordo com os layouts deveria parecer um círculo na tela, torna-se óbvio que no SceneKit o objeto do rótulo deve ser uma esfera (já que a projeção da esfera em qualquer plano é um círculo). Para que a projeção tenha um determinado raio na tela especificado nos requisitos dos projetistas, é necessário conhecer o raio da esfera a qualquer momento. Assim, ao colocar uma esfera de um determinado raio na cena em um determinado ponto e atualizar constantemente seu raio ao se aproximar ou se afastar, obteremos uma projeção na tela do tamanho necessário a qualquer momento. O algoritmo para determinar o raio da esfera em um ponto arbitrário no tempo é o seguinte:


  1. definir a posição do objeto no palco - o centro da esfera,
  2. encontre a projeção desse ponto no plano da tela (usando a API SceneKit),
  3. para determinar o tamanho exigido da marca na tela, encontramos a distância da câmera ao centro da esfera no palco,
  4. determinamos o tamanho necessário na tela pela distância do objeto usando as regras descritas no design,
  5. sabendo o tamanho da marca na tela (diâmetro do círculo), escolhemos qualquer ponto desse círculo,
  6. faça a projeção reversa (UnprojectPoint) do ponto selecionado,
  7. encontramos o comprimento do vetor desde o ponto recebido no palco até o centro da esfera.

O valor obtido do comprimento do vetor será o raio desejado da esfera.


terminar-marcador-tamanho-solução-cena-kit


No momento da implementação, não conseguimos encontrar uma maneira de determinar o tamanho do objeto em cena e decidimos desenhar a marca de chegada usando o UIKit. Nesse caso, o algoritmo repete as etapas de 1 a 5, após o qual um círculo do tamanho desejado é desenhado na tela com o centro no ponto obtido na etapa 2 usando as ferramentas UIKit. Um exemplo de implementação de um rótulo usando o UIKit pode ser encontrado aqui .


Algumas palavras sobre o código

No final do artigo, forneci vários links para materiais úteis e simplesmente interessantes, incluindo exemplos, nos quais é possível ver em detalhes o código real que resolve os problemas apresentados no artigo e implementa os algoritmos apresentados. O principal interesse na minha opinião é o protótipo de roteamento de pedestres , que reúne toda a funcionalidade, com exceção do mecanismo de ajuste de eixo, descrito em detalhes abaixo.


O código acima não afirma ser ótimo, completo e de qualidade de produção =)


A diferença entre o uso do SceneKit e do UIKit nesse caso também está no fato de que, ao implementar no SceneKit, o objeto SCNNode para o ponto de extremidade da rota (marca de chegada) será criado com material e geometria, pois deve estar visível ao usar o UIKit precisamos do objeto nó exclusivamente para procurar a projeção no plano da tela (para determinar o centro da marca na tela). Nesse caso, a geometria e o material não precisam ser adicionados. Observe que a distância da câmera ao objeto SCNNode do ponto final da rota pode ser encontrada de duas maneiras - usando as coordenadas geográficas dos pontos ou como o comprimento do vetor entre os pontos na cena. Isso é possível porque o objeto da câmera é uma propriedade SCNNode. Para obter o nó da câmera, você precisa consultar a propriedade pointOfView da nossa cena.


Aprendemos como determinar o raio do nó da marca de chegada em um momento arbitrário ao implementar no SceneKit e a posição da exibição da marca de chegada, se implementada no UIKit. Resta entender quando é necessário atualizar esses valores? Este local é o método do objeto SCNSceneRendererDelegate:


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

Este método é chamado após cada quadro de cena renderizado. Ao atualizar os valores da propriedade no corpo desse método, obtemos um rótulo de acabamento exibido corretamente.


Animação


Depois que a marca de chegada apareceu no dev, passamos a adicionar animação ondulada a essa marca. Eu acho que, para a maioria dos desenvolvedores do iOS, criar animações não é grande coisa. Mas, ao pensar sobre o método de implementação, encontramos o problema de atualizar constantemente o quadro de nossa visão. Observe que, na maioria dos casos, as animações são adicionadas aos objetos estáticos do UIView. Um problema semelhante - uma atualização constante do raio da geometria do nó surge quando implementada usando o SceneKit. O fato é que a animação pulsante se resume à animação do tamanho do círculo (para UIKit) e do raio da esfera (para SceneKit). Sim, sim, sabemos que no UIKit esse tipo de animação pode ser feito usando o CALayer, mas, para simplificar a narrativa, decidi considerar esse problema simetricamente para as duas estruturas. Considere uma implementação no UIKit. Se você adicionar código que anima o mesmo quadro ao código existente que atualiza o quadro de exibição, a animação será interrompida definindo explicitamente o quadro. Portanto, como solução para esse problema, decidimos usar a animação da propriedade transform.scale.xy do objeto UIView. Ao implementar usando o SceneKit, você precisará adicionar animação da propriedade scale ao objeto SCNNode. O bom de usar o SceneKit nesse caso é o fato de que ele suporta totalmente o CoreAnimation, portanto, não é necessário aprender uma nova API. O código que implementa a animação semelhante à animação do rótulo no Yandex.Maps é mais ou menos assim:


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

Quadro de avisos


No início do artigo, mencionei um outdoor com uma distância do ponto final da rota, que, em essência, é uma etiqueta com texto sempre localizado acima da marca de chegada. Por tradição, descreverei os problemas inerentes às implementações no UIKit e SceneKit, falando sobre possíveis soluções para cada uma das estruturas.


Vamos começar com o UIKit. Nesse caso, o outdoor é um UILabel comum, no qual o texto é constantemente atualizado, mostrando a distância até o ponto final da rota. Vamos olhar para o problema que estamos enfrentando.


terminar-marcador-de-problema-local-uikit


Se você definir um rótulo para um quadro e girar o telefone, veremos que o quadro não muda (seria estranho se não estivesse). Ao mesmo tempo, gostaríamos que o rótulo permanecesse paralelo ao plano da Terra.


terminar-marcador-outdoor-desejado-uikit


Acho que todo mundo entende que, ao mudar a orientação do dispositivo, precisamos mudar o rótulo, mas em que ângulo? Se você ativar a imaginação e imaginar mentalmente todos os eixos dos sistemas de coordenadas e vetores envolvidos nesse processo, podemos concluir que o ângulo de rotação é igual ao ângulo entre o eixo x do sistema de coordenadas UIKit e a projeção do eixo X do sistema de coordenadas SceneKit no plano da tela.


finish-placemark-billboard-solution-uikit


Uma tarefa simples que mais uma vez comprovou a utilidade do curso de geometria da escola.


Ao implementar a marca de chegada usando o SceneKit, você provavelmente terá que renderizar o cartaz à distância usando as ferramentas do SceneKit, o que significa que você definitivamente terá a tarefa de fazer com que o objeto SCNNode esteja sempre orientado para a câmera. Acho que o problema ficará mais claro se você olhar para a figura:


finish-placemark-billboard-problem-scenekit


Esse problema foi resolvido usando a API SCNBillboardConstraint. Adicionando uma constante com um eixo livre Y à coleção de construções de nosso nó, obtemos um nó que gira em torno do eixo Y do seu sistema de coordenadas, de modo a estar sempre orientado para a câmera. A única tarefa do desenvolvedor é colocar esse nó na altura correta para que o quadro de avisos com a distância fique sempre visível para o usuário.


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

Etiqueta do assistente


Uma das principais características do roteamento de pedestres com realidade aumentada, dentro da equipe, consideramos uma marca auxiliar - um elemento visual especial que aparece na tela no momento em que o ponto final da rota sai da zona de visibilidade e mostra ao usuário onde virar o telefone para que a marca apareça na tela linha de chegada.


acabamento-marcador-dica-visão geral


Tenho certeza que muitos dos leitores encontraram funcionalidades semelhantes em alguns jogos, na maioria das vezes em atiradores. Que surpresa nossa equipe foi quando vimos esse elemento da interface do usuário nos layouts. Devo dizer imediatamente que a implementação correta desse recurso pode exigir mais de uma hora de experimentação de você, mas o resultado final vale o tempo gasto. Começamos definindo requisitos, a saber:


  • para qualquer orientação do dispositivo, o rótulo se move ao longo das bordas da tela,
  • se o usuário girou 180 graus para o ponto final da rota, o rótulo é exibido na parte inferior da tela,
  • a cada momento, girar em direção à marca deve ser a curva mais curta até o ponto final da rota.

Após descrever os requisitos, iniciamos a implementação. Quase imediatamente, chegamos à conclusão de que a renderização seria feita usando o UIKit. O principal problema com a implementação foi a determinação do centro desse rótulo em cada momento. Depois de considerar a marca de chegada, essa tarefa não deve causar dificuldades, por isso não vou me deter em sua solução em detalhes. No artigo, apenas darei uma descrição do algoritmo para a escolha do centro do rótulo auxiliar, e o código fonte pode ser encontrado aqui .


Algoritmo de Centro de Pesquisa Algoritmo de Pesquisa:


  1. crie um objeto SCNNode para o ponto final da rota com uma posição na cena obtida da coordenada geográfica do ponto,
  2. encontre a projeção de um ponto no plano da tela,
  3. encontre a interseção do segmento do centro da tela até o ponto da projeção encontrada com os segmentos dos limites da tela no sistema de coordenadas da tela.

terminar-marcador-sugestão-solução


O ponto de interseção encontrado é o centro desejado da marca auxiliar. Por analogia com o código que atualiza os parâmetros do rótulo final, colocamos o código que renderiza o rótulo auxiliar no método delegado já mencionado acima.


Polilinha de rota


Tendo construído uma rota e visto a marca de chegada na tela, o usuário pode alcançá-la orientando apenas na direção da marca, mas o roteamento é chamado assim porque mostra a rota para o usuário. Pensamos que seria muito estranho reduzir a funcionalidade do roteamento de pedestres, excluindo a exibição da rota da versão AR. Para visualizar a linha de rota, decidiu-se exibir um conjunto de setas se movendo ao longo dela. Nesse caso, os projetistas estavam convencidos de que as flechas praticamente desapareceriam ao se afastar (o tamanho seria determinado pelas regras da projeção em perspectiva) e decidiu-se usar o SceneKit para implementação.


Antes de continuar a descrever a implementação, é importante observar que, por design, as setas deveriam estar a uma distância de 3 m uma da outra. Se você estimar o número de objetos (setas) que precisam ser renderizados com uma rota de cerca de 1 km, serão aproximadamente 330 peças. Ao mesmo tempo, cada objeto é adicionado a uma animação de movimento ao longo de sua parte da rota. Observe que as setas remotas da posição da câmera no palco a uma distância de cerca de 100-150 metros são praticamente invisíveis devido ao seu tamanho pequeno. Tendo considerado esses fatores, decidiu-se não exibir todos os objetos, mas exibir apenas aqueles que são removidos do usuário a não mais de 100 metros ao longo da linha de rota, atualizando periodicamente o conjunto de objetos exibido. Exibimos uma quantidade suficiente de informações visuais, eliminando cálculos desnecessários do SceneKit e economizando a bateria do usuário.


visão geral da rota-polilinha


Vejamos as principais etapas que tivemos que realizar para obter o resultado final:


  • seleção da seção de rota para a qual exibiremos as primitivas,
  • criação de modelos 3D,
  • criação de animação
  • atualizar ao dirigir ao longo de uma rota.

Selecionando uma plotagem para exibir


Como observei acima, não exibimos setas para toda a rota, mas selecionamos a seção ideal para exibição. A escolha de um segmento em um momento arbitrário consiste em procurar o segmento de rota mais próximo (a rota é uma sequência de segmentos / segmentos) até a posição atual do usuário e selecionar segmentos da rota mais próxima ao ponto final da rota até que seu comprimento total exceda 100 metros.


rota-polilinha-rota-parte-seleção


Criação de modelo 3D


Considere com mais detalhes o processo de criação de um modelo 3D. Na maioria dos casos, tudo o que você precisa fazer para criar um modelo 3D simples (como nossa seta) é abrir qualquer editor 3D, passar algum tempo dominando-o e criar esse modelo nele. Caso os funcionários de sua equipe tenham experiência em modelagem 3D ou tenham tempo para aprender, por exemplo, 3DMax (e deve ser comprado), você terá uma sorte incrível. Infelizmente, no momento da implementação desse recurso, nenhum de nós tinha nenhuma experiência especial, não havia tempo livre para treinamento, portanto tivemos que fazer um modelo, por assim dizer, com meios improvisados. Quero dizer a descrição do modelo no código. Tudo começou com a apresentação de um modelo 3D na forma de triângulos. Em seguida, tivemos que encontrar manualmente as coordenadas dos vértices desses triângulos no sistema de coordenadas do modelo e criar uma matriz de índices dos vértices dos triângulos. Com esses dados à nossa disposição, podemos criar a geometria necessária diretamente no SceneKit. Você pode criar um modelo semelhante ao nosso, por exemplo, assim:


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

O resultado final é assim:


rota-polilinha-seta-modelo


Animação da linha de rota


O próximo passo no caminho para exibir uma linha animada da rota foi o estágio de criação da própria animação. Mas qual é a maneira de realizar a animação, que, na forma final, parece que a seta inicia seu movimento no ponto inicial da seção selecionada da rota e "flutua" ao longo da rota até o final desta seção?



Não descreverei todas as formas possíveis para criar uma animação desse tipo; em vez disso, abordarei mais detalhadamente o método que escolhemos. Depois que uma seção da rota é selecionada, a dividimos em seções do mesmo comprimento - seções da animação de uma seta. Cada uma dessas seções é destacada em cores e tem um comprimento igual à distância entre as setas.


rota-polilinha-rota-parte-particionamento


No início de cada seção, criamos o objeto SCNNode da seta, cuja animação consiste em mover-se ao longo de sua seção.


rota-polilinha-setas-posição-inicial


Como você pode ver, a seção de animação às vezes consiste em um segmento, às vezes em dois ou mais. Tudo depende do passo (no nosso caso - 3 metros) entre as setas e as coordenadas dos pontos que compõem o percurso.


Uma animação de seta é uma sequência de duas etapas:


  • aparência na posição inicial com o ângulo de rotação inicial,
  • uma sequência de deslocamentos ao longo de segmentos com rotações nos pontos de conexão dos segmentos.

Esquematicamente, fica assim:


route-polyline-arrow-anitaion-steps


Pareceu-nos a maneira mais fácil de implementar essa animação usando a API SCNAction - uma API declarativa que permite criar convenientemente animações sequenciais, de grupo e repetidas. Você pode ver a implementação em mais detalhes aqui . Devido ao fato de que cada seta termina sua animação no ponto inicial da seção de animação da próxima seta, é criada a impressão de movimento contínuo da seta ao longo de toda a seção selecionada da rota.


Sobre isso, proponho concluir a consideração de vários aspectos da renderização e ir para a parte principal - determinar as posições dos objetos no palco pelas coordenadas geográficas dos objetos.


Determinando a posição de um objeto na cena


Iniciamos a conversa sobre como determinar a posição de um objeto na cena considerando sistemas de coordenadas, cuja conversão deve ser realizada. Existem apenas 2 deles:


  • coordenadas geodésicas (ou geográficas por simplicidade) - a posição dos objetos (pontos de rota) no mundo real,
  • Coordenadas cartesianas - a posição dos objetos em cena (no ARKit). Lembre-se de que o sistema de coordenadas da cena coincide com o sistema de coordenadas ARKit (no caso de usar o ARSCNView).

A tradução de um sistema de coordenadas para outro e vice-versa é possível devido ao fato de que as coordenadas no ARKit são medidas em metros, e o deslocamento entre duas coordenadas geodésicas pode ser traduzido com grande precisão no deslocamento em metros ao longo dos eixos X e Z do sistema de coordenadas ARKit em pequenas compensações. Deixe-me lembrá-lo de que as coordenadas geodésicas são pontos com uma certa longitude e latitude.


Vamos relembrar conceitos importantes do curso de geografia, como paralelos e meridianos, e suas propriedades básicas:


  • Paralelo é uma linha com um valor de grau de latitude. Os comprimentos dos vários paralelos são diferentes.
  • Meridiano - uma linha com um valor de grau de longitude. Os comprimentos de todos os meridianos são os mesmos.

Agora vamos ver como você pode calcular o deslocamento em metros, entre duas coordenadas geodésicas com coordenadas \ inline (lat_1, lon_1) e \ inline (lat_2, lon_2) :


\ Delta x = \ Delta long \ 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 ^ {°}}


Explicação

O deslocamento em coordenadas geodésicas é mapeado linearmente para metros apenas em pequenos deslocamentos. Em grandes deslocamentos, é necessário aceitar honestamente a integral.


Agora que podemos traduzir o deslocamento de um sistema de coordenadas para outro, precisamos decidir sobre um ponto de referência - um ponto para o qual a coordenada geográfica e a coordenada no ARKit (coordenada no palco) são conhecidas ao mesmo tempo. Tendo encontrado esse ponto, podemos determinar a coordenada de qualquer objeto no palco, conhecendo sua coordenada geográfica e usando as fórmulas acima.


Para maior clareza, considere um exemplo:
No início da sessão de realidade aumentada, solicitamos ao CoreLocation nossa coordenada geográfica e a recebemos instantaneamente - \ inline (lat_0, lon_0) . Lembrando que a origem do sistema de coordenadas do ARKit está no início da sessão no ponto em que o dispositivo está localizado, obtivemos o ponto de referência, pois sabemos a coordenada geográfica e a coordenada em cena \ inline (x_0, y_0, z_0) = (0,0,0) . Precisamos encontrar a coordenada na cena do objeto com uma coordenada geográfica \ inline (lat_1, lon_1) . Para fazer isso, encontre o deslocamento em metros entre a coordenada geográfica do objeto e a coordenada geográfica do nosso ponto de referência e adicione o deslocamento encontrado à coordenada na cena do ponto de referência. A coordenada resultante na cena será a desejada.


coordenadas-conversão-objeto-posição-em-cena


Observo que a posição na cena encontrada dessa maneira corresponderá à posição do objeto no mundo real somente se o eixo X / Z do sistema de coordenadas da cena estiver alinhado com as direções para o sul / leste. O alinhamento do eixo, em teoria, deve ser alcançado definindo o sinalizador worldAlignment como gravitiAndHeading. Mas como eu disse no começo do post, isso está longe de ser sempre o caso.


Vamos considerar com mais detalhes o método de determinação do ponto de referência. Para fazer isso, introduzimos o conceito de estimativa - um conjunto de coordenadas geográficas e coordenadas no palco.


coordenadas-conversão-estimativa-definição


O método proposto acima para determinar o ponto de referência nem sempre pode ser usado. No momento do início de uma sessão de realidade aumentada, uma solicitação de CLLocation de um usuário não pode ser executada imediatamente; além disso, a precisão da coordenada obtida pode ter um grande erro. Seria mais correto solicitar ao SceneKit uma posição no palco no momento em que obtivermos o valor do CoreLocation. Nesse caso, os componentes da estimativa resultante são realmente obtidos ao mesmo tempo e temos a oportunidade de usar qualquer uma das estimativas como ponto de referência. Ao trabalhar com o ARKit, o erro de deslocamento se acumula com o tempo, portanto a Apple não recomenda o uso do ARKit como ferramenta de navegação.


Quando decidimos implementar o roteamento de pedestres com realidade aumentada, fizemos uma pequena pesquisa sobre as soluções existentes naquele momento, usando o ARKit para tarefas semelhantes, e nos deparamos com a estrutura ARKit + CoreLocation. A idéia dessa estrutura era que, graças ao ARKit, podemos determinar com mais precisão a localização do usuário do que quando usamos exclusivamente CoreLocation.


Conceito de ARKit + CoreLocation:


  • ao receber CLLocation de CLLocationManager
    • solicite uma posição na cena usando scene.pointOfView.worldPosition
    • salve este par de coordenadas (estimativa) no buffer
  • obtenha a localização exata, se necessário
    • escolha a melhor estimativa
    • calcular o deslocamento entre a posição atual no palco e a posição no palco da melhor estimativa

, , CoreLocation, .


, « ». , .


(, ):


  • ( horizontalAccuracy),
  • ,
  • 100 .

CoreLocation . , , CoreLocation , 100 .


, . , , ( 100 ).



, X/Z ARKit / . ARKit , , .


Porque

, (, 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) em conformidade.


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) e \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


, , .


. , . Por exemplo:


  • N ,
  • ,
  • M ( ?).

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


, . , , ( , , ).


Teste


, . , , , . 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 . , . .


Em vez de uma conclusão


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


Links úteis



, . .

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


All Articles