flowchart TD
A["a pessoa dá um passo<br/>à frente"] --> B["ouvido interno relata<br/>DESLOCAMENTO"]
A --> C["sistema mede só<br/>a orientação"]
C --> D["a cena desenhada<br/>fica onde estava"]
D --> E["olhos relatam<br/>CORPO PARADO"]
B --> F{"dois relatos<br/>incompatíveis"}
E --> F
F --> G["mal-estar"]
1 Dispositivos, rastreamento e graus de liberdade — Resumo
Versão de revisão. As cinco definições deste módulo estão aqui inteiras, e as ressalvas também — elas são o que se erra quando se revisa às pressas. O que encolheu foi o número de exemplos. O tratamento longo está na versão completa deste capítulo e no capítulo correspondente do livro.
Alguém põe um visor no rosto pela primeira vez, olha em volta e sorri. O mundo desenhado gira com a cabeça, na direção certa e na hora certa. Então essa pessoa dá um passo à frente, e o mundo fica onde estava.
Guarde esse passo. Ele volta no fim, com a conta feita.
Em segundos vem o desconforto; em minutos, algo pior. Quem monta a demonstração aprende a contornar o assunto — pede que a pessoa fique parada, sugere que sente — e trata o sintoma como fragilidade de quem usa. É uma conta faltando. Aquele aparelho media três coisas sobre a cabeça e ignorava outras três: as medidas dizem para onde a cabeça aponta, e as ignoradas diriam onde ela está.
1.1 O corpo percebe antes de você nomear
O mal-estar tem causa geométrica, e ela se explica sem uma palavra de fisiologia avançada.
Dentro de cada ouvido interno há estruturas que medem movimento. Uma parte responde a rotação; outra, a aceleração linear. Os dois sinais chegam ao cérebro junto com o que os olhos veem, e a expectativa de coerência entre eles é permanente. Quando a pessoa virou a cabeça, tudo bateu. Quando ela deu o passo, o ouvido relatou deslocamento e os olhos relataram uma cena imóvel — que é o que se vê quando o corpo está parado.
Duas informações que não podem ser ambas verdadeiras, e nenhum critério para descartar uma. O desconforto não depende de a cena ser feia nem de o visor ser barato: um aparelho caro com a mesma lacuna produz o mesmo efeito, em melhor resolução. E daí sai a correção que contraria o instinto — sem saber para onde a pessoa se deslocou, o certo é não mover a cena. Uma cena movida por suposição erra em direção e em quantidade, e acrescenta um terceiro relato ao desacordo que já havia entre dois.
Separe dois casos que a conversa comum mistura. Um sistema pode não saber onde a pessoa está, e um sistema pode saber com atraso. O segundo é mais traiçoeiro porque tudo parece funcionar: quem usa diz apenas que “está um pouco estranho”, sem conseguir nomear o defeito, já que nada do que ele vê está no lugar errado. Está no tempo errado. Atraso melhora por graus, com menos trabalho por quadro; ausência de medida não melhora por graus, porque é uma metade da pose que ninguém estima.
1.2 Seis números, e três deles medidos
A briga entre visores caros e baratos cabe numa contagem de parâmetros independentes.
Um livro sobre a mesa precisa de três números para ter uma quina localizada, e de mais três para dizer para que lado ele aponta. Nenhum dos seis se deduz dos outros.
A pose de um corpo rígido é o par formado pela sua posição e pela sua orientação. Cada metade tem três parâmetros independentes, e cada parâmetro independente é um grau de liberdade.
Um sistema que estima só a orientação rastreia três graus de liberdade; um que estima as duas metades rastreia seis. A distinção é de quantos parâmetros o sistema mede, e não de quantos o corpo tem: o corpo tem sempre seis.
flowchart LR
P["pose de um<br/>corpo rígido"] --> R["orientação<br/>3 parâmetros"]
P --> T["posição<br/>3 parâmetros"]
R --> S3["sistema de TRÊS graus<br/>estima só a orientação"]
R --> S6["sistema de SEIS graus<br/>estima as duas metades"]
T --> S6
S3 -.-> N["andar é invisível:<br/>a cena não se move"]
S6 -.-> M["andar move a cena<br/>na direção e na medida certas"]
A definição não diz nada sobre precisão, frequência ou método — um sistema de seis graus pode estimar mal as duas metades e continuar sendo de seis. E “visor de três graus de liberdade” é frase sobre o sistema de medida, embora o uso comum a trate como frase sobre o aparelho. O objeto tem os mesmos seis de sempre; o que fica menor é a descrição que o sistema produz dele.
Um sistema de três graus não está quebrado. Veja o caso da fotografia esférica: ela não ganha nada com rastreamento de posição, porque a imagem está no infinito. O problema aparece quando a cena tem objetos a distâncias finitas. Aproximar-se de uma bancada a um metro deveria aumentar o tamanho aparente dela, e o corpo espera isso com a precisão de quem acerta a mão numa maçaneta no escuro. A saída honesta admite o limite — objetos distantes, interação por direção do olhar, nenhuma promessa de escala corporal. A desonesta constrói como se a posição existisse e chama de “limitação do hardware” o enjoo que se segue.
Agora a parte incômoda: a plataforma não informa quantos graus o aparelho rastreia. Não há propriedade a ler. A camada que responde não sabe o que o sensor faz, sabe o que o sistema do aparelho aceitou entregar. O que sobra é inferir a partir dos espaços de referência concedidos. Um espaço ancorado no chão exige que o aparelho saiba onde o chão está, e um aparelho que só gira não sustenta essa relação enquanto a pessoa caminha.
a inferência sem aposta, em devices/graus.ts
export function grausDeLiberdade(leitura: LeituraDeEspacos): GrausDeLiberdade {
const temChao: boolean =
leitura.concedidos.includes('local-floor') ||
leitura.concedidos.includes('bounded-floor') ||
leitura.concedidos.includes('unbounded');
if (temChao) {
return 'seis';
}
if (leitura.concedidos.length === 1 && leitura.concedidos[0] === 'viewer') {
return 'tres';
}
return 'indeterminado';
}
A linha que decide tudo é a última: existe um terceiro valor, indeterminado, para o caso em que a evidência não fecha. Devolver só tres ou seis obrigaria a escolher um lado no caso ambíguo, e escolher ali é apostar. A regra da inferência está escrita por extenso no próprio arquivo, porque inferência sem premissa exposta não pode ser contestada.
a regra por extenso, em devices/graus.ts
/**
* Regra da inferência, escrita por extenso porque é ela que você precisa
* poder contestar:
*
* - `local-floor` ou `bounded-floor` concedidos exigem que o aparelho saiba onde
* está o chão em relação a quem observa. Um visor que só gira não tem como
* sustentar isso, então a concessão é evidência forte de posição rastreada.
* - `viewer` sozinho é o mínimo que qualquer sessão entrega: a origem acompanha
* quem observa, e translação alguma é observável a partir dela. Evidência forte
* da ausência.
* - `local` no meio do caminho é ambíguo de verdade. A especificação o descreve
* como origem próxima de quem observa no início da sessão, e um aparelho de três
* graus pode concedê-lo mantendo a posição sempre na origem. Daí
* `indeterminado`, e não uma aposta.
*/
Guarde o limite dessa inferência. Ela lê concessões da plataforma, e concessão é decisão tomada por razões que podem não ter nada a ver com o sensor. Uma plataforma que concedesse o espaço ancorado no chão a um aparelho de três graus faria a inferência devolver seis. Por isso a função devolve uma leitura, e não um veredito.
1.3 Duas maneiras de saber onde se está
Posição é sempre posição em relação a alguma coisa, e alguém tem de decidir onde essa coisa mora.
Um arranjo de rastreamento é a disposição física do que mede a pose, e admite dois casos. No arranjo por infraestrutura externa, o que mede está fixo no ambiente e o corpo rastreado é observado de fora. No arranjo por sensores embarcados, o que mede viaja com o corpo, e a pose é estimada a partir do que esses sensores observam do ambiente.
A classificação é pela posição do que mede. Ela não diz nada sobre precisão, custo ou geração do equipamento.
Essa última frase impede o mal-entendido mais comum: existem arranjos externos imprecisos e embarcados excelentes, e a ordem entre dois equipamentos é pergunta sobre produtos.
flowchart TB
Q["onde está o que mede<br/>a pose?"] --> E["INFRAESTRUTURA EXTERNA<br/>fixo no ambiente,<br/>observa o corpo de fora"]
Q --> I["SENSORES EMBARCADOS<br/>viajam com o corpo,<br/>observam o ambiente"]
E --> E1["funciona só no<br/>espaço preparado"]
E --> E2["falha por oclusão:<br/>algo entre instrumento e corpo"]
E --> E3["a região ruim é da SALA<br/>e fica onde está"]
I --> I1["funciona em qualquer<br/>ambiente que ofereça<br/>o que observar"]
I --> I2["falha por ambiente<br/>pobre em referências"]
I --> I3["a região ruim ANDA<br/>junto com a pessoa"]
O ancestral do primeiro arranjo é de 1968. O visor que Ivan Sutherland montou naquele ano ficava suspenso do teto do laboratório por um braço mecânico articulado, e o braço media a posição da cabeça, lendo os ângulos das próprias juntas. Geometria rígida conhecida no lugar do sensor que estima, com a precisão paga em imobilidade — o alcance do movimento era o alcance do braço. O braço não foi adotado por ser a melhor forma concebível de medir uma cabeça: ele já estava lá segurando o peso, e ler os ângulos era a informação mais barata disponível. O apelido “espada de Dâmocles” veio depois, dado por outras pessoas.
A diferença que mais muda o projeto está no modo de falhar. Num arranjo externo, a região problemática é da sala: descoberta uma vez, continua no mesmo lugar amanhã. Num arranjo embarcado, ela viaja com a pessoa. Com instalação fixa você testa a sala e afirma que o sistema funciona nela; sem instalação, cada sala é um caso novo, e o que se pode afirmar é como o ambiente se comporta diante da degradação.
E é o arranjo embarcado que sustenta um ambiente aberto por uma URL em três aparelhos diferentes. Em troca, ele transfere ao aparelho a tarefa de descobrir onde está. O aparelho desenha a planta da sala enquanto atravessa a sala, e corrige o desenho toda vez que reconhece um canto por onde já passou.
Dado um corpo que se desloca por um ambiente desconhecido, o problema de localização e mapeamento simultâneos consiste em estimar, ao mesmo tempo, a trajetória do corpo e um mapa do ambiente, a partir apenas das observações feitas pelos sensores que viajam com ele.
As duas estimativas são mutuamente dependentes, e ambas são revisáveis — uma observação nova pode alterar retroativamente o que já se julgava conhecido.
flowchart LR
subgraph CIRC["o círculo, desfeito pela simultaneidade"]
POS["posição estimada<br/>melhor aposta atual"] --> MAP["mapa estimado<br/>melhor aposta atual"]
MAP --> POS
end
OBS["observação nova<br/>do sensor"] --> CIRC
CIRC --> REC{"reconhece um lugar<br/>por onde já passou?"}
REC -->|"não"| ACU["o erro pequeno<br/>continua acumulando"]
REC -->|"sim, e é o lugar certo"| COR["correção RETROATIVA:<br/>mapa e trajetória revistos"]
REC -->|"sim, mas é lugar errado"| DOB["o mapa dobra<br/>sobre si mesmo"]
O círculo é real: para saber onde está, o aparelho precisa do mapa; para desenhar o mapa, precisa saber onde está. A saída abandona a ordem e faz as duas coisas ao mesmo tempo, com as duas produzindo estimativas. Reconhecer um lugar, porém, custa caro — comparar a vista atual contra tudo o que já foi visto cresce com o percurso, e o resultado precisa sair dentro de um quadro. Guarda-se, então, uma descrição compacta de cada lugar e verifica-se em detalhe só os candidatos que sobrevivem ao filtro barato. Disso decorre algo que ninguém adivinharia: reconhecer é mais provável quando se olha o lugar do mesmo ângulo de antes.
Repare que os dois erros possíveis têm custos muito diferentes. Deixar de reconhecer custa uma oportunidade de correção; reconhecer um lugar onde nunca se esteve dobra o mapa sobre si mesmo, e o estrago vale retroativamente. Por isso esses algoritmos exigem evidência forte antes de aceitar um reencontro, e quem os observa de fora chama de lentidão o que é cuidado com preço.
Antes de seguir. Se a posição informada pode ser corrigida retroativamente, o que acontece com um objeto virtual cujas coordenadas você anotou dez minutos atrás? Responda antes de ler o parágrafo seguinte.
A posição informada é estimativa sujeita a revisão, e não medida de régua: duas leituras no mesmo lugar físico podem divergir sem que nenhuma esteja errada, porque entre elas o mapa foi corrigido. Daí a resposta sobre prender um objeto ao mundo. Anotar coordenadas funciona por alguns minutos e escorrega, porque as coordenadas envelhecem quando o mapa que as define é corrigido embaixo delas. Ancorar consiste em pedir ao aparelho que mantenha o objeto preso a um ponto do mapa e corrija as coordenadas sozinho.
Duas ressalvas fecham o assunto. A correção melhora a coerência do espaço já mapeado, e não a precisão do próximo passo. E a plataforma não entrega o mapa: não há nuvem de pontos, contagem de pontos nem aviso de correção. Chegam quatro efeitos — a pose a cada quadro, o raio lançado contra superfície real, os planos reconhecidos e a âncora. Um ambiente correto tolera correções que nunca vê chegar.
1.4 A sala limpa é a pior sala
Um aparelho que se orienta por cantos precisa de cantos. São três as condições que o deixam sem eles.
A primeira é a superfície sem textura: uma parede lisa, sem quadro, sem tomada e sem rodapé no campo da câmera não oferece ponto distinguível dos vizinhos. A segunda é a iluminação pobre, e aqui o problema é o contraste — mesmo com textura, a câmera precisa distinguir claros e escuros para extrair um canto. A terceira é o movimento brusco: um giro rápido borra a imagem, e um canto borrado deixa de ser um canto justamente nos quadros em que a continuidade era mais necessária.
As três juntas descrevem uma sala recém-pintada de branco, bem iluminada por luz difusa — que qualquer pessoa chamaria de limpa e adequada, e que para o aparelho é uma sala sem informação. Luz abundante não compensa nada: ela ilumina bem uma superfície que não tem o que mostrar.
flowchart TD
T["superfície sem textura"] --> SC["nenhum canto<br/>a reconhecer"]
L["iluminação pobre"] --> SC
M["movimento brusco"] --> SC
SC --> QSP["o quadro chega<br/>e a pose não vem"]
MEN["menu do sistema aberto:<br/>a sessão saiu de foco"] --> QSP
QSP --> C{"a sessão estava<br/>visível naquele quadro?"}
C -->|"sim"| DEG["conta como<br/>degradação de rastreamento"]
C -->|"não"| NOR["comportamento normal<br/>do sistema, não conta"]
As três têm relação diferente com o tempo. Falta de textura é permanente enquanto ninguém pregar um cartaz na parede. Luz fraca é intermitente e obedece ao relógio, o que produz o relato irreprodutível de que “às vezes trava”. Movimento brusco é a única que depende de quem usa, e por isso a única sobre a qual a interface tem alavanca. Repare: um ambiente que põe o objeto de interesse atrás de quem observa está pedindo o giro que degrada o rastreamento.
Do lado de quem programa, a plataforma oferece um único indício: o quadro chega e a pose não vem. Um quadro assim, isolado, é normal; uma sequência deles é a degradação acontecendo, e a diferença entre as duas coisas é contagem que o programa faz sozinho.
a distinção que salva o diagnóstico, em devices/estabilidade.ts
/**
* Acumula a contagem quadro a quadro.
*
* O parâmetro `visivel` é o que separa perda de rastreamento de sessão que saiu
* de foco. Quando quem usa abre o menu do sistema do visor, a sessão continua
* viva, os quadros continuam chegando e a pose deixa de vir — e contar isso como
* falha de sensor acusaria o aparelho de um defeito que é comportamento normal
* do sistema operacional.
*/
export class ContadorDeEstabilidade {
private quadros: number = 0;
private quadrosSemPose: number = 0;
private quadrosOcultos: number = 0;
private maiorLacuna: number = 0;
private lacunaCorrente: number = 0;
public registrar(temPose: boolean, visivel: boolean): void {
O segundo parâmetro impede um diagnóstico falso. Quando alguém abre o menu do sistema do visor para ver a hora, a sessão continua viva, os quadros continuam chegando e a pose deixa de vir. Sintoma idêntico, causa que é comportamento normal do sistema operacional — e um contador que ignorasse a diferença reprovaria o aparelho toda vez que alguém consultasse o relógio.
O contador conta e não julga: a interpretação mora numa função separada. Um medidor que interpreta embute a expectativa de quem o escreveu, e passa a concordar sempre com essa pessoa. Mantendo a contagem burra, o número pode ser usado como argumento contra quem o produziu. A frase de diagnóstico, do outro lado, também se recusa a apontar qual das três causas ocorreu — daqui não se distingue textura de luz e de movimento, e escolher uma seria inventar o diagnóstico junto com a medida.
1.5 O que se pergunta a um aparelho
Quantas classes de aparelho existem? Três perguntas feitas em tempo de execução dispensam a resposta.
Uma capacidade declarada é uma propriedade que a plataforma responde quando interrogada sobre o aparelho, e não uma propriedade do aparelho. São três as famílias interrogáveis: os tipos de sessão suportados, os recursos opcionais concedidos a uma sessão aberta e as fontes de entrada que a sessão apresenta.
A resposta pertence à camada que responde. Um aparelho pode ter um sensor cuja capacidade correspondente a plataforma não declara, e o programa não tem como usar o que não foi declarado.
flowchart TB
F["fora de qualquer sessão"] --> P1["que tipos de sessão<br/>este aparelho aceita abrir?"]
P1 --> R1["sim / não / não sei"]
G["gesto de quem usa<br/>abre a sessão"] --> D["dentro da sessão"]
D --> P2["que recursos opcionais<br/>foram concedidos?"]
D --> P3["que fontes de entrada<br/>a sessão apresenta?"]
P2 --> R2["concedido / negado / indeterminado"]
P3 --> R3["tem raio? tem pose própria?"]
F -.->|"perguntar aqui o que<br/>só existe lá produz suposição"| D
Um celular com câmera e sensores, aberto num navegador que não implementa a interface de sessões imersivas, tem tudo o que seria necessário e declara nada. Para o programa, a capacidade não existe. A tentação é completar a declaração por dedução — este visor é da geração tal, logo deve ter aquilo —, que é a origem de uma família de defeitos que só aparecem no aparelho de outra pessoa.
Da terceira família, duas propriedades decidem quase tudo: se a fonte tem raio, isto é, direção de apontamento, e se ela tem pose própria, isto é, se é rastreada no espaço. Um dedo na tela tem a primeira e não a segunda; um controle empunhado tem as duas; um mouse não tem nenhuma. A lista muda durante o uso — um controle ligado no meio da sessão aparece nela.
A consulta sobre tipos de sessão parece binária, e bastaria um valor lógico. Bastaria, se ela sempre respondesse.
a resposta que admite não saber, em modes/verificacao.ts
/**
* `desconhecido` não é sinônimo de `nao`. O navegador sem a API XR não está
* dizendo que o aparelho não serve — está dizendo que não sabe responder, e
* tratar as duas coisas como a mesma é o erro que faz um relatório honesto
* virar um relatório confiante e errado.
*/
export type Suporte = 'sim' | 'nao' | 'desconhecido';
Uma linha de tipo decide o comportamento de um relatório inteiro. O terceiro valor obriga todo consumidor a tratar o caso em que não se sabe, e o verificador reclama de quem esquecer. Um relatório que diz não suporta onde deveria dizer não sei afirma, sobre o aparelho de quem lê, um fato errado.
Duas restrições da plataforma decidem a forma de qualquer sonda. Metade das respostas só existe dentro da sessão — recursos concedidos, composição do fundo, espaços de referência e fontes de entrada são propriedades da sessão, e a consulta externa devolve suposição bem formatada. E abrir sessão imersiva exige gesto de quem usa: o pedido que não vem de um clique é recusado, e a recusa chega como exceção com nome em inglês. Some-se o terceiro obstáculo, que derruba mais demonstrações que os dois: sessão imersiva exige contexto seguro, e o certificado autoassinado do servidor de desenvolvimento produz um aviso que precisa ser aceito em cada aparelho, uma vez. Antes disso o botão não reage, e o sintoma não diz por quê.
1.6 Concedido, negado, e o que ninguém respondeu
Uma sessão aberta com pedido de recursos opcionais devolve a lista do que foi habilitado. O que fazer com o que ficou de fora dela?
O que se sabe sobre um recurso opcional, depois de a sessão abrir, assume exatamente um de três valores. Concedido: a sessão reportou a lista dos recursos habilitados e o nome está nela. Negado: a sessão reportou a lista e o nome não está nela. Indeterminado: a sessão não reportou lista alguma.
O terceiro valor é ausência de resposta, e não resposta negativa. Confundi-los produz um relatório que afirma, com a mesma sintaxe das outras linhas, um fato que ninguém verificou.
flowchart TD
A["a sessão abriu"] --> B{"ela reportou a lista<br/>de recursos habilitados?"}
B -->|"não reportou"| I["INDETERMINADO<br/>ausência de resposta"]
B -->|"reportou"| C{"o nome do recurso<br/>está na lista?"}
C -->|"sim"| G["CONCEDIDO<br/>pode ser usado"]
C -->|"não"| N["NEGADO<br/>recusado, e sem razão exposta"]
I -.->|"o atalho que estraga tudo"| N
os três estados, em devices/recursos.ts
/**
* O que se sabe sobre um recurso depois de a sessão abrir.
*
* - `concedido`: o nome está em `enabledFeatures`, e o recurso pode ser usado.
* - `negado`: a sessão reportou a lista e o nome não está nela — o aparelho ou o
* navegador recusou, e a razão não é exposta.
* - `indeterminado`: a sessão não reportou lista alguma. Não é negativa; é
* ausência de resposta, e tratá-la como negativa produz relatório confiante e
* errado.
*/
export type EstadoDeRecurso = 'concedido' | 'negado' | 'indeterminado';
O mesmo endereço aberto em dois navegadores mostra a diferença. Num deles a lista vem e três recursos aparecem como negados; no outro a lista não vem, e os mesmos três aparecem como indeterminados. Perceba que as duas telas se parecem e afirmam coisas diferentes. E a razão da negativa nunca é exposta pela plataforma — um programa que a registrasse estaria inventando.
O padrão geral extrapola o assunto: sempre que um valor lógico registra o resultado de uma consulta que pode não responder, a ignorância vira negativa em silêncio. O sintoma tem assinatura reconhecível — o sistema fica mais confiante quanto menos informação recebe. E a distinção só sobrevive se os três estados forem carregados juntos até o ponto de decisão; qualquer camada intermediária que simplifique para dois destrói a informação sem erro de compilação.
Pedir um recurso exige o nome exato da especificação, sem tradução e sem tolerância a erro de digitação — escrever errado produz um pedido que não é atendido, sem mensagem. Daí o catálogo, e o campo que importa nele é o segundo.
o catálogo com motivo, em devices/recursos.ts
Cada entrada carrega o motivo de estar ali, em uma frase. O campo é opcional para a máquina e é o que impede a lista de crescer sozinha: cada recurso pedido é uma coisa a mais que a sessão pode negar e um caso a mais a tratar. E a lista precisa estar decidida antes de a sessão existir — não dá para abrir, olhar em volta e pedir mais coisas depois.
Neste ponto do percurso, a Bancada ainda não desenha nada. O que ela ganhou foi a capacidade de perguntar, com o relatório escrito onde alguém consiga lê-lo com o aparelho no rosto. Note que não existe console de depuração dentro de um visor: erro registrado ali é indistinguível de um programa que decidiu não fazer nada. A linha mais valiosa desse relatório é a que compara a composição do fundo declarada no marco anterior com a que a sessão informou. Quando os dois valores não batem, a frase diz qual dos dois estava errado, e não é o aparelho.
Volte ao passo do começo. O sistema estimava a orientação e ignorava a posição, o ouvido interno relatou uma translação que os olhos não confirmaram, e o mal-estar veio do desacordo. Cada peça daquela cena tem nome agora, e nenhuma delas foi decidida por quem escreve o ambiente — todas chegam prontas, do outro lado de uma interface. A única alavanca disponível é perguntar antes de supor, que é exatamente o que um primeiro artefato sem um único polígono já consegue fazer.