一触即发

在你的 Windows Phone 中浏览虚拟世界

Charles Petzold

下载代码示例

Charles Petzold直到哥白尼的时间 — — 并连续多年后 — — 人们认为宇宙建造一系列的地球周围的同心天体球。虽然这将宇宙的模型已被放弃,但它仍是天体的方便聘请的标识与自己作为观众的 3D 空间中的对象的位置的概念。

天体是特别方便的程序,让您查看一个虚拟现实的世界中使用智能手机或现实。这种程序,你握手机,如果你把一张照片或透过镜头,但你在屏幕上看到视频可能没有什么要做和真实的世界。

这样一个程序需要确定其在 3D 空间中的定位,以便由弧扫电话用户可以通过这个虚拟的世界平移。我在此列中的最后一部分中所述的运动传感器,Windows Phone 是能够提供必要的定位信息。

我们从天体运动传感器所提供的资料的翻译有如何?它是所有关于坐标系统。

我们都熟悉允许我们的地理坐标描述我们星球的表面上的一个位置。地球表面上的任何点可以由两个数字表示:纬度和经度,两者均与地球的中心中的一个顶点的角度。纬度是相对于赤道的角度:它是赤道以北的位置的积极和消极的位置南。北极的纬度是 90 ° 和南极的纬度为-90 °。经度涉及通过测量在两根杆子从子午线,这是通过,位于英国格林威治的经度线的大圆之间的角度。

我们生活不仅一个球体,但表面上还在概念的天体的中心。几个坐标系统可以用来表示这个天体的位置,但我会专注一个叫做水平坐标系统,因为它根据在地平线上。

水平坐标

使用您伸出一只胳膊,指向你看看你周围的任何对象。该对象已在天球上的位置。该位置是什么?将你直的手臂移动向上或向下,使它成为水平 — — 就是平行于地球的表面。在这种运动过程中通过你的手臂摆动的角度叫做海拔高度。

积极的海拔高度值是在地平线上 ; 负值是地平线以下。位于直线上升,从你的对象有 90 °,也称为天顶的高度,一直向下走的对象-90 °,称为谷底的高空。

现在摆动你水平的伸出的手臂,所以它指向的北。在这种运动过程中你的手臂摆动的角度叫做方位。海拔高度和方位一起构成的水平坐标。

请注意水平坐标为您提供有关如何遥远的东西是任何信息。在日食,太阳和月亮有相同的水平坐标。使用任何类型的天体的坐标系统,一切都被假定为内部表面的天体。

方位角必须是相对于罗盘中的特定点。大多数情况下,方位是设置在北部,增加向东移动的角度为 0 °。然而,天文学家们往往会在越来越多角度西迁 ; 南设置 0 ° 至少这是如何让 · Meeus 总结它在他经典的书,"天文算法"(螨铃,1998年)。

除非是不同角度,类似于地理坐标,水平坐标。而不是一个球体的表面上,你是在看出去的中心。方位是经度相媲美,海拔高度是纬度相媲美。像经度的圆圈,圆的方位总是通过两极的大圆。像纬度的圆圈,圆的海拔高度始终是彼此平行的。地平线作为地理坐标在赤道水平坐标中扮演相同的角色。

现在拿起你的 Windows Phone,抓住它,所以你看你的相机镜头点同时屏幕。相机镜头所指的方向都有特定的海拔高度和方位。虽然这水平坐标从概念上讲是天体球的内政上的某个位置,它也是一个镜头的视角的方向 — — 数学的范畴,一个三维向量。

由于之前的专栏中,我已讨论过,这款手机有隐式的坐标系统中,积极的 Z 轴位置延伸从屏幕。这就意味着其他侧点 (0,0,– 1) 的 3D 向量的方向的镜头。此列中的以前安装的示 (msdn.microsoft.com/magazine/jj190811),在 Windows Phone 中的运动传感器,可以获得一个 3D 旋转矩阵,它描述了地球旋转与电话的方式。若要获取一个矩阵,它描述了电话相对地球的旋转方式,必须倒从运动传感器获得的矩阵:

matrix = Matrix.Invert(matrix);

使用此倒立的矩阵旋转 (0,0,– 1) 向量:

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

现在,您有一个 3D 的向量,描述了相机镜头所指的方向。 该向量需要转换为海拔高度和方位角度。

如果电话保持直立 — — 那就是,与转化载体水平到地球的表面 — — 的 Z 分量为 0,和从二维笛卡尔坐标到极坐标的知名转换问题减少了。 在 C# 中,它就是:

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

这是一个以弧度表示的角度。 乘以 180,除以 π 将转换为度。

此公式意味着,北东的方向已为零和值增加的方位。

如果您更喜欢南为零地增加值西进的方向发展,转变,结果由 180 ° 通过更改符号的 X 和 Y 的组件。

该公式的方位角真的有效无论变换矢量的 Z 分量。

Z 分量是海拔高度的正弦值。 因为海拔范围仅在正负 90 ° 之间,它可以计算使用反正弦函数:

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

同样,乘以 180 °,除以 π 将弧度转换为度。

不过,我们仍缺少一些事情,你可能会认识到当你意识到我们已经变成三维旋转矩阵已只有两个维度,因为它仅限于内部表面的天体的坐标。

当你在电话中描述的一个 3D 的载体,某个特定方向的目的是,然后旋转周围像轴矢量电话时,会发生什么? 矢量不会改变,也不在高度和方位的值,但虚拟现实场景手机的屏幕上应与手机旋转。

这额外的议案有时被称为倾斜。 它也是一个角度,但计算是有点难度比海拔高度和方位。

您可以看到,在 HorizontalCoordinate 结构中创建转换为一项议案,读到海拔高度、 方位和倾斜,所有以度为单位计算。 此结构包含在 AltitudeAndAzimuth 项目,它是这篇文章的下载代码之间。 此程序只需使用运动传感器获取的电话时,方向然后将信息转换为水平坐标。 这项工程需要引用 (运动类) 的 Microsoft.Devices.Sensors 大会和 Microsoft.Xna.Framework 大会 (用于 3D 矢量和矩阵)。 屏幕显示的已转换的向量和 HorizontalCoordinates 结构中的值。 图 1 电话举行约直立的镜头显示约东指出,有点按顺时针方向倾斜。

The AltitudeAndAzimuth Display
图 1 AltitudeAndAzimuth 显示

获取大图片

假设您想要查看要远远大于您计算机的屏幕的图像 — — 或者,在此情况下,您的电话。

传统上,还参与了滚动条。 在触摸屏上滚动条,可以消除,用户可以执行类似的滚动操作,使用手指。

但另一种方法是从概念上讲壁纸天体与此大图像的内部,然后查看通过移动手机本身。 (请记住当您移动电话要查看图像,要不移动电话向左、 右或向上和向下在平面中。 不结盟运动必须沿弧形这样的海拔高度和方位正在改变。)

如何大可以这样的形象是这样,它沿在屏幕中的自然方式随着手机移动?

平均 Windows Phone 屏幕可能是大约 2 英尺宽 3.33 英寸高。 如果您持有电话 6 英寸从你的脸上,一些手机占据的一个字段的简单三角揭示了查看约 19 ° 度宽和高 31 °。 这两个字段的视图在横向模式中,拿着电话,是从总的方位 360 ° 度和 180 度的海拔高度的切片。 然后,这款手机的屏幕很粗略举行 6 英寸从横向模式你脸上占有大约 10%的总视图字段水平和垂直方向。

或者,它认为这种方式:如果您想要使用您的电话中纵向模式以平移表面的位图,该位图可以介于 8000 像素宽和 4800 像素高的地区。

这是它们项目,其中包含的链接下载八个图像 (绘画、 照片、 文档和图纸,大多来自维基百科的混合),最大的是 5649 像素宽和 4000 像素高的想法。 您可以轻松地添加其他图像通过编辑 XML 文件,但根据我的经验,使用 PictureDecoder.DecodeJpeg 方法,你可能会遇到 — 内存不足异常,如果你去要大得多。

后台文件传输

考虑到它们所引用的文件都超过 2 MB 的大小,其中之一是 19 MB,这似乎是一个理想的机会,使的图像的大部分设施的使用添加到 Windows Phone 将在后台文件下载。

在它们的程序中,大多数的网页的致力于维护列表框,其中列出了可用的文件,并将它们下载到的独立存储。 图 2 显示一些程序图像已下载 (它为缩略图所示),一个下载进度和其他尚未下载。

The BigPicture Main Page
它们主要页图 2

若要使用后台文件传输,您创建一个 BackgroundTransferRequest,向其传递的外部文件的 URL 类型的对象和中的某个位置的 URL 隔离存储在 /shared/transfers 目录内。 虽然该程序正在运行,并且当您的程序在重新启动时,您可以枚举活动的请求,您就可以得到状态和通过事件进展的变化。

当它们程序启动时,网页搜索独立的存储的任何可能已先前下载的图像。 我发现直接到文件名指定的而不使用某些其他文件名的临时文件下载文件。 这意味着我的程序遇到了的文件,有尚未完全下载,或其下载可能已被取消。 我修正了一些在我的程序中使用的 /shared/transfers 目录,仅下载文件而不是永久存储。 当下载完成后时,该程序将该文件移到另一个目录,并在另一个目录中创建一个缩略图。 为方便起见,所有三个文件具有相同的名称,但通过他们发现在其中的目录进行区分。

当它们的已下载一个文件时,您可以点击列表框中的项和程序导航到这,这是该程序的真正心。

查看大图

这有两种查看模式,您可以在屏幕上攻交替。 动画将您从一种模式到其他。

在正常模式下,显示在图 3,其像素大小,在概念上伸展到的天体内部中会显示该图像。 您导航在图像周围通过更改定位的电话,向您想要查看的天体的区域概念上指向电话。 (它可能会帮助,如果你站起来和不同的方向,把你的整个身体并向上和向下点电话以及。)

BigPicture Showing One Small Part of a Large Painting
图 3 它们显示大型绘画的一小部分

当您点击电话时,你转移到缩小模式。 整个图像将显示未旋转的纵向模式,如中所示图 4。 一个矩形显示的图像的都是在正常模式下查看的部分。 在此示例中,该矩形是右下角附近。

BigPicture Showing an Entire Large Painting
图 4 它们显示整个大型绘画

在边缘会怎样? 因为位图从概念上讲延伸到内部的天体,当您移动电话到以外的右边缘的位图的权利),你应该再遇到的左边的缘。 然而,在 Silverlight 中的布局系统不会环绕以这种方式。 如果该程序允许对面大位图的边缘是可见的然后将需要两个图像元素。 在全部四个角相交的点,需要四个图像元素。

我拒绝这一概念。 超出的右边缘的位图是有差距等于电话显示的最大尺寸,然后左边会出现。 你看不到两个显示中的边缘。 这也解决了在两极,在理论上顶部和底部的这幅画应压缩到一个点做什么的问题。

图 5 ,这显示大部分的 XAML 文件。 当然,图像元素显示的位图本身,与无的拉伸属性设置表示它将显示在其像素大小。 通常大图像将被裁剪掉布局系统对大小显示,和你不能泛周围的图像的其余部分。 但把画布内一切诱骗布局系统呈现整个对象。 具有嵌入式矩形边框是一个矩形,可见在缩小模式中,但也可见拥抱在正常模式下屏幕的内侧。 名为 imageTransform 的 CompositeTransform 适用于图像和边框。 其他复合变换命名为 borderTransform 仅适用于边界。

图 5 的 XAML 文件,查看页面的它们图像

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

隐藏文件开始运动传感器去,然后应用旋转矩阵来创建一个 HorizontalCoordinate 对象,用来设置这些两个转换的属性。 这类还定义了一个 InterpolationFactor 依赖项属性,是两种查看模式之间的过渡到动画的目标。 因为 InterpolationFactor 从 0 到 1 动态的该视图转换之间正常和缩小。

图 6 显示所涉及的大部分的数学题。 运动传感器,则更新时出现的最重要的计算之一。 这是计算的 CenterX 和 CenterY 属性的图像,CompositeTransform,它是哪里的海拔高度和方位来发挥。 虽然此变换中心是围绕点,缩放和旋转发生,计算进一步将这点放在中心的正常查看模式中显示。 矩形边框也与这点对齐。

图 6 多为它们变换数学

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

当方位是 0 (朝北的电话),海拔高度为 0 (垂直) CenterX 和世纪属性被设置为位图的中心。 这样,这些 CenterX 和世纪的属性可以设置为位图之外的值,请注意 maxDimension 值列入。 当你扫过去的边缘,这允许进行填充。

计算的其余的大部分发生在 UpdateImageTransforms 方法,称为当运动传感器报告一个新值,或在过渡期间更改 InterpolationFactor 属性的过程中。 这里是缩放和图像变换的翻译出现的位置,以及旋转。

如果你有兴趣了解这些转换的相互作用,您可能想要清理通过消除插值的所有代码。 检查的简化的公式时 InterpolationFactor 是 0,它为 1 时,你就会看到它们实际上非常简单。

查尔斯 · Petzold 是 MSDN 杂志,长期贡献和 Windows 8 的当前正在更新他的经典著作"编程 Windows"(微软出版社,1998年)。 他的网站是 charlespetzold.com

衷心感谢以下技术专家对本文的审阅:音莫尔斯