Este artigo foi traduzido por máquina.

Toque e ouça

Exibindo um mundo virtual do seu Windows Phone

Charles Petzold

Baixar o código de exemplo

Charles PetzoldAté o tempo de Copérnico — e por muitos anos depois — as pessoas acreditavam que o universo foi construído em uma série de esferas concêntricas de celestial em torno da terra. Apesar de que o modelo do universo foi abandonado, é ainda conveniente empregar o conceito de uma esfera celeste para identificar a localização de objetos no espaço 3D em relação a mesmos como espectadores.

Uma esfera celeste é particularmente útil para programas que permitem que você use um smartphone para a visualização de um mundo de realidade virtual ou realidade aumentada. Com esses programas, você segure o telefone como se você está tirando uma fotografia ou vídeo através da lente da câmera, mas o que você vê na tela pode não ter nada a ver com o mundo real.

Tal programa deve determinar sua orientação no espaço 3D, de modo que varrendo o telefone em Arcos o usuário pode navegar neste mundo virtual. Com o sensor de movimento que descrevi no último artigo desta coluna, Windows Phone é capaz de fornecer a informação necessária orientação.

Como podemos traduzir informação fornecida pelo sensor de movimentos para a esfera celeste? É tudo sobre o sistema de coordenadas.

Estamos todos familiarizados com coordenadas geográficas que nos permitem descrevem uma localização na superfície do nosso planeta. Qualquer ponto da superfície da terra pode ser indicado por dois números: latitude e longitude, que são ângulos com um vértice no centro da terra. Latitude é um ângulo em relação à linha do Equador: É positivo para locais ao norte do Equador e negativo para o sul de locais. A latitude do pólo norte é de 90º e o latitude do Pólo Sul é de-90 °. Longitude envolve ângulos entre os grandes círculos que passam através dos dois pólos medidos a partir do meridiano, que é o meridiano que passa por Greenwich, Inglaterra.

Vivemos não só na superfície de uma esfera, mas também no centro de uma esfera conceitual de celeste. Vários sistemas de coordenadas pode ser usados para designar locais sobre esta esfera celeste, mas o que eu estarei focando é chamado sistema de coordenadas horizontal, porque baseia-se no horizonte.

Coordenadas horizontais

Usando seu braço estendido, aponte para qualquer objeto que você vê ao seu redor. Esse objeto tem uma localização na esfera celeste. O que é esse local? Move seu braço direito para cima ou para baixo, assim torna-se horizontal — isto é, paralela à superfície da terra. O ângulo que seu braço balança através de durante este movimento é chamado a altitude.

Valores positivos de altitude são acima do horizonte; valores negativos são abaixo do horizonte. Um objeto localizado diretamente acima de você tem uma altitude de 90 °, também chamado de zenith, e um objeto em linha reta para baixo tem uma altitude de-90 °, chamado nadir.

Agora gire seu braço estendido horizontal assim que ele está apontando para norte. O ângulo de que seu braço oscila durante este movimento é chamado de azimute. A altitude e azimute juntos constituem uma coordenada horizontal.

Observe que a coordenada horizontal não dá-lhe nenhuma informação sobre como algo que está longe. Durante um eclipse solar, o sol e a Lua têm a mesma coordenada horizontal. Com qualquer tipo de sistema de coordenadas celestes, tudo é considerado na superfície interior da esfera celeste.

O azimute deve ser em relação a um determinado ponto na bússola. Maioria das vezes, o azimute é fixado em 0 ° para o norte, com o aumento de ângulos, movendo-se para o leste. No entanto, os astrônomos tendem a definir 0 ° no Sul, com aumento de ângulos que se deslocam para o oeste; pelo menos é como Jean Meeus resume-se em seu livro clássico, "Algoritmos astronômicos" (Willmann-Bell, 1998).

Coordenadas horizontais são análogas às coordenadas geográficas, exceto a perspectiva é diferente. Em vez de ser na superfície de uma esfera, você está no centro, olhando para fora. O azimute é comparável a longitude e a altitude é comparável à latitude. Como círculos de longitude, círculos de azimute são sempre grandes círculos passando através dos pólos. Como os círculos de latitude, círculos de altitude são sempre paralelos entre si. Horizonte desempenha o mesmo papel nas coordenadas horizontais, como o Equador em coordenadas geográficas.

Agora pegar seu Windows Phone e prendê-lo, então você está olhando para a tela enquanto os pontos de lente de câmera longe de você. A direção que está apontando a lente da câmera tem uma determinada altitude e azimute. Embora essa coordenada horizontal é conceitualmente uma localização no interior da esfera celeste, é também um sentido do ponto de vista da lente da câmera — matematicamente, um vetor tridimensional.

Como já abordei em colunas anteriores, o telefone tem um sistema de coordenadas implícito, onde o eixo z positivo se estende para fora da tela. Isso significa que a lente da câmera a outros pontos laterais na direção do vetor 3D (0, 0, – 1). Como demonstrei na edição anterior desta coluna (msdn.microsoft.com/magazine/jj190811), o sensor de movimento em Windows Phone permite-lhe obter uma matriz de rotação 3D que descreve como a terra é girada em relação ao telefone. Para obter uma matriz que descreve como o telefone é girado em relação à terra, a matriz obtida pelo sensor de movimentos deve ser invertida:

matrix = Matrix.Invert(matrix);

Use esta matriz invertida para girar a (0, 0, – 1) vector:

Vector3 vector = Vector3.Transform(new Vector3(0, 0, -1), matrix);

Agora você tem um vetor 3D que descreve a direção que da lente da câmera está apontando. Esse vetor precisa ser convertido para ângulos de altitude e azimute.

Se o telefone é mantido na posição vertical — isto é, com a transformada de vetor horizontal à superfície da terra — o componente de z é 0, e o problema reduz a conversão conhecidos a partir de coordenadas cartesianas bidimensionais de coordenadas polares. Em c#, é simplesmente:

double azimuth = Math.Atan2(vector.X, vector.Y);

Que é um ângulo em radianos. Multiplique por 180 e dividir por π para converter em graus.

Esta fórmula implica que o norte tem um azimute de zero e aumento de valores no sentido leste.

Se você preferir Sul a zero com o aumento de valores em uma direção para o oeste, deslocar o resultado por 180 °, alterando o sinal dos componentes x e Y.

Que a fórmula para o azimute é realmente válida, independentemente do componente z do vetor de transformação.

Esse componente de z é o seno de altitude. Porque a altitude varia entre positivo e negativo de 90 °, pode ser calculado usando a função seno inverso:

double altitude = Math.Asin(vector.Z);

Novamente, multiplicar por 180 ° e dividir por π para converter radianos em graus.

No entanto, nós ainda está faltando alguma coisa, que você pode reconhecer quando você perceber que nós traduzimos uma matriz de rotação tridimensional em uma coordenada que possui apenas duas dimensões, pois limita-se a superfície interior da esfera celeste.

O que acontece quando você apontar o telefone em uma determinada direção, descrito por um vetor 3D e, em seguida, gira o telefone ao redor do vetor como um eixo? O vetor não muda, nem fazer a altitude e azimute valores, mas o virtual cena de realidade na tela do telefone deve girar em relação ao telefone.

Este movimento extra é por vezes referido como tilt. É também um ângulo, mas o cálculo é um pouco mais difícil do que a altitude e azimute.

Você pode ver esse cálculo em uma estrutura de HorizontalCoordinate que criei que converte um movimento lendo em altitude, azimute e inclinação, em graus. Esta estrutura está incluída no projeto AltitudeAndAzimuth, que está entre o código para download neste artigo. Este programa simplesmente usa o sensor de movimento para obter a orientação do telefone e, em seguida, converte as informações de coordenadas horizontais. Este projeto requer referências para o assembly de Microsoft.Devices.Sensors (para a classe de movimento) e o assembly Microsoft.Xna.Framework (para a matriz e vetor 3D). A tela exibe o vetor transformado e os valores da estrutura HorizontalCoordinates. Figura 1 mostra o telefone realizado aproximadamente na vertical com a lente apontada aproximadamente leste e inclinada para a direita um pouco.

The AltitudeAndAzimuth Display
Figura 1: A exibição de AltitudeAndAzimuth

Começ o retrato grande

Suponha que você deseja exibir uma imagem que é muito maior que a tela do seu computador — ou, neste caso, seu telefone.

Tradicionalmente, envolveram-se as barras de rolagem. Em uma tela de toque, as barras de rolagem podem ser eliminadas e o usuário pode executar uma operação de rolagem similar, usando os dedos.

Mas uma outra abordagem é conceitualmente o interior da esfera celestial com esta imagem grande de papel de parede e, em seguida, visualizá-lo, movendo o próprio telefone. (Tenha em mente que quando você mover o telefone para ver a imagem, você não está movendo o telefone à esquerda e direita ou cima e para baixo em um avião. O movimento tem que ser ao longo de Arcos, para que a mudança de altitude e azimute.)

Como grande tal imagem possível para que as panelas através da tela de uma forma natural como o telefone se move?

Uma tela média de Windows Phone é, provavelmente, cerca de 2 centímetros de largura e 3,33 cm de altura. Se você segurar o telefone 6 polegadas do seu rosto, alguma trigonometria simples revela que o telefone ocupa um campo de exibir cerca de 19 ° graus largura e altura 31 °. Segurando o telefone em modo paisagem, estes dois campos de visão são fatias de azimute total de 360 ° graus e altitude de 180 °. Muito aproximadamente, em seguida, a tela do telefone realizada 6 centímetros do seu rosto no modo paisagem ocupa cerca de 10 por cento do total do campo de visão horizontal e verticalmente.

Ou pense desta forma: Se você quiser utilizar seu telefone em modo retrato para se deslocar sobre a superfície de um bitmap, esse bitmap pode ser em algum lugar na região de 8000 pixels de largura e 4800 pixels de altura.

Essa é a idéia do projeto BigPicture, que contém links para download de oito imagens (uma mistura de pinturas, fotografias, documentos e desenhos, principalmente da Wikipedia), é a maior das quais 5649 pixels de largura e 4000 pixels de altura. Você pode facilmente adicionar outras imagens, editando um arquivo XML, mas baseado na minha experiência usando o método de PictureDecoder.DecodeJpeg, você é provável encontrar exceções de falta de memória, se você for muito maior.

Transferências de arquivos de plano de fundo

Considerando que a maior parte da imagem arquivos referenciados por BigPicture são mais do que 2 MB de tamanho e um deles é de 19 MB, isso parecia uma oportunidade ideal para fazer uso da instalação adicionado ao Windows Phone para fazer o download de arquivos em segundo plano.

No programa BigPicture, dedica-se a maioria do MainPage para manter uma caixa de listagem que lista os arquivos disponíveis e transferi-los para o armazenamento isolado. Figura 2 mostra o programa com algumas imagens já baixei (que são exibidos como miniaturas), um download em andamento e outros ainda não baixado.

The BigPicture Main Page
Figura 2 página principal BigPicture

Para utilizar a transferência do arquivo de plano de fundo, criar um objeto do tipo BackgroundTransferRequest, passando-lhe a URL do arquivo externo e a URL de um local isolado armazenamento dentro do diretório /shared/transfers. Em seguida, você pode obter mudanças no status e o progresso por meio de eventos enquanto o programa estiver sendo executado, e você pode enumerar as solicitações ativas quando o programa for iniciado novamente.

Quando inicia o programa BigPicture, MainPage procura armazenamento isolado para qualquer imagens que talvez tenha sido baixado anteriormente. Descobri que os arquivos são baixados diretamente para o nome do arquivo que você especificar e não em um arquivo temporário com algum outro nome de arquivo. Isso significa que meu programa estava encontrando arquivos que não tinha ainda sido totalmente baixado, ou cujos downloads podem ter sido canceladas. Fixei vários bugs no meu programa, usando o diretório de /shared/transfers apenas para baixar arquivos e não para armazenamento permanente. Quando um download for concluído, o programa se move o arquivo para outro diretório e cria uma miniatura em outro diretório. Para sua conveniência, todos os três arquivos têm o mesmo nome, mas distinguem-se pelo diretório em que eles são encontrados.

Quando um arquivo foi baixado por BigPicture, você pode bater o item no ListBox e o programa navega para ViewPage, que é o verdadeiro coração do programa.

Visualização da imagem grande

ViewPage tem dois modos de visualização, você pode alternar entre tocando na tela. Uma animação leva você de um modo para o outro.

No modo normal, mostrado na Figura 3, a imagem é exibida em seu tamanho de pixel, conceitualmente esticado no interior de uma esfera celeste. Você navegar ao redor da imagem, alterando a orientação do telefone, conceitualmente apontando o telefone para a área da esfera celestial que você deseja exibir. (Pode ajudar se você se levantar e transformar todo o seu corpo em diferentes direções e aponte o telefone acima e para baixo também).

BigPicture Showing One Small Part of a Large Painting
Figura 3 BigPicture mostrando uma pequena parte de uma grande pintura

Quando você bate o telefone, você alternar para o modo de zoom-out. A imagem inteira é exibida unrotated no modo retrato, como mostrado na Figura 4. Um retângulo exibe a parte da imagem que é visível no modo normal. Neste exemplo, o retângulo é perto do canto inferior direito.

BigPicture Showing an Entire Large Painting
Figura 4 BigPicture mostrando uma grande pintura de toda a

O que acontece nas bordas? Porque o bitmap conceitualmente é estendido para o interior de uma esfera celeste, quando você mover o telefone para a direita além da borda direita do bitmap, você deve então encontrar a borda esquerda. No entanto, o sistema de layout em Silverlight não envolver em torno desta forma. Se o programa oposta bordas de um bitmap grande para ser visível, dois elementos de imagem seria necessários. O ponto onde todos os quatro cantos, quatro elementos de imagem seria necessários.

Eu vetado esse conceito. Além da borda direita do bitmap é uma distância igual à dimensão máxima do visor do telefone e, em seguida, aparece a borda esquerda. Você nunca verá ambas as bordas no display. Isso também resolve o problema do que fazer nos pólos, onde teoricamente superior e inferior da pintura devem ser compactadas a um ponto.

Figura 5 mostra que a maioria do arquivo XAML para ViewPage. O elemento de imagem exibe o bitmap em si, é claro, e a configuração para a propriedade Stretch nenhum indica que é para ser exibida em seu tamanho de pixel. Normalmente uma imagem grande iria ser cortada pelo sistema de layout para o tamanho da tela, e você não seria capaz de deslocar o resto da imagem. Mas colocar tudo dentro de um Canvas engana o sistema de layout em processar o objeto inteiro. A fronteira com o retângulo incorporado é o retângulo visível no modo de zoom-out, mas é também visível abraçando dentro da tela em modo normal. O CompositeTransform chamado imageTransform se aplica para a imagem e a borda. O outro composto-Transform chamado borderTransform só se aplica a fronteira.

Figura 5 O arquivo XAML para a página de visualização de imagens de BigPicture

<phone:PhoneApplicationPage ...
>
  <Grid x:Name="LayoutRoot" Background="Transparent">
    <Canvas>
      <Grid>
        <Image Name="image" Stretch="None" />
        <Border Name="outlineBorder"
                BorderBrush="White"
                HorizontalAlignment="Left"
                VerticalAlignment="Top">
            <Rectangle Name="outlineRectangle"
                       Stroke="Black" />
            <Border.RenderTransform>
              <CompositeTransform x:Name="borderTransform" />
            </Border.RenderTransform>
        </Border>
        <Grid.RenderTransform>
          <CompositeTransform x:Name="imageTransform" />
        </Grid.RenderTransform>
      </Grid>
    </Canvas>
    <TextBlock Name="titleText"
               Style="{StaticResource PhoneTextNormalStyle}"
               Margin="12,17,0,28" />
    <TextBlock Name="statusText"
               Text="creating image..."
               HorizontalAlignment="Center"
               VerticalAlignment="Center" />
  </Grid>
</phone:PhoneApplicationPage>

O arquivo code-behind começa um curso de sensor de movimento e, em seguida, aplica-se a matriz de rotação para criar um objeto de HorizontalCoordinate que ele usa para definir as propriedades dessas duas transformações. ViewPage classe também define uma propriedade de dependência de InterpolationFactor que é o destino de uma animação para a transição entre os dois modos de visualização. Como InterpolationFactor é animado de 0 a 1, a vista faz a transição entre o normal e o zoom-out.

Figura 6 mostra que a maioria da matemática envolvida. Um dos cálculos mais importantes ocorre quando o sensor de movimento é atualizado. Este é o cálculo do CenterX e CenterY Propriedades de CompositeTransform para a imagem, e é onde a altitude e azimute entram em jogo. Embora este centro de transformação é o ponto em torno que escala e rotação ocorre, mais cálculos colocar este ponto no centro da tela no modo de visualização normal. A borda Retangular também está alinhada com este ponto.

Figura 6 muito da transformação matemática para BigPicture

public partial class ViewPage : PhoneApplicationPage
{
  ...
void OnLoaded(object sender, RoutedEventArgs args)
  {
    // Save the screen dimensions
    screenWidth = this.ActualWidth;
    screenHeight = this.ActualHeight;
    maxDimension = Math.Max(screenWidth, screenHeight);
    // Initialize some values
    outlineBorder.Width = screenWidth;
    outlineBorder.Height = screenHeight;
    borderTransform.CenterX = screenWidth / 2;
    borderTransform.CenterY = screenHeight / 2;
    // Load the image from isolated storage
    ...
// Save image dimensions
    imageWidth = bitmap.PixelWidth;
    imageHeight = bitmap.PixelHeight;
    ...
zoomInScale = Math.Min(screenWidth / imageWidth, 
      screenHeight / imageHeight);
    UpdateImageTransforms();
    ...
}
  ...
void OnMotionCurrentValueChanged(object sender,
          SensorReadingEventArgs<MotionReading> args)
  {
    ...
// Get the rotation matrix & convert to horizontal coordinates
    Matrix matrix = args.SensorReading.Attitude.RotationMatrix;
    HorizontalCoordinate horzCoord = 
      HorizontalCoordinate.FromMotionMatrix(matrix);
    // Set the transform center on the Image element
    imageTransform.CenterX = (imageWidth + maxDimension) *
      (180 + horzCoord.Azimuth) / 360 - maxDimension / 2;
    imageTransform.CenterY = (imageHeight + maxDimension) *
      (90 - horzCoord.Altitude) / 180 - maxDimension / 2;
    // Set the translation on the Border element
    borderTransform.TranslateX = 
      imageTransform.CenterX - screenWidth / 2;
    borderTransform.TranslateY = 
      imageTransform.CenterY - screenHeight / 2;
    // Get rotation from Tilt
    rotation = -horzCoord.Tilt;
    UpdateImageTransforms();
  }
  static void OnInterpolationFactorChanged(DependencyObject obj,
              DependencyPropertyChangedEventArgs args)
  {
    (obj as ViewPage).UpdateImageTransforms();
  }
  void UpdateImageTransforms()
  {
    // If being zoomed out, set scaling
    double interpolatedScale = 1 + InterpolationFactor * 
      (zoomInScale - 1);
    imageTransform.ScaleX =
    imageTransform.ScaleY = interpolatedScale;
    // Move transform center to screen center
    imageTransform.TranslateX = 
      screenWidth / 2 - imageTransform.CenterX;
    imageTransform.TranslateY = 
      screenHeight / 2 - imageTransform.CenterY;
    // If being zoomed out, adjust for scaling
    imageTransform.TranslateX -= InterpolationFactor *
      (screenWidth / 2 - zoomInScale * imageTransform.CenterX);
    imageTransform.TranslateY -= InterpolationFactor *
      (screenHeight / 2 - zoomInScale * imageTransform.CenterY);
    // If being zoomed out, center image in screen
    imageTransform.TranslateX += InterpolationFactor *
      (screenWidth - zoomInScale * imageWidth) / 2;
    imageTransform.TranslateY += InterpolationFactor *
      (screenHeight - zoomInScale * imageHeight) / 2;
    // Set border thickness
    outlineBorder.BorderThickness = 
      new Thickness(2 / interpolatedScale);
    outlineRectangle.StrokeThickness = 2 / interpolatedScale;
    // Set rotation on image and border
    imageTransform.Rotation = (1 - InterpolationFactor) * rotation;
    borderTransform.Rotation = -rotation;
  }
}

Quando o azimute é 0 (telefone de frente para o Norte) e a Altitude 0 (vertical), o CenterX e CenterY Propriedades é definido para o centro do bitmap. Observe a inclusão do valor maxDimension para que essas propriedades CenterX e CenterY podem ser definidas para valores fora do bitmap. Isso permite o preenchimento quando você varrer as bordas do passado.

A maioria do restante dos cálculos ocorrem durante o método de UpdateImageTransforms, que é chamado quando o sensor de movimento relata um novo valor ou quando a propriedade de InterpolationFactor durante as transições. Aqui é onde a escala e a tradução de transformar a imagem ocorre, bem como de rotação.

Se você estiver interessado em compreender a interação dessas transformações, convém limpá-los, eliminando todo o código de interpolação. Examinar as fórmulas simplificadas quando InterpolationFactor é 0 e quando é 1, e você verá que eles são na verdade bastante simples.

Charles Petzold é um colaborador de longa data para MSDN Magazine e atualmente está atualizando seu clássico livro "Programming Windows" (Microsoft Press, 1998) para o Windows 8. Seu site é charlespetzold.com.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Donn Morse