2012/2/9
Motion API 对于创建使用设备方向和空间运动作为输入机制的 Windows Phone 应用程序非常有用。有一些 API 可以用于从设备的 Compass、Gyroscope 和 Accelerometer 传感器获取原始传感器数据,但运动 API 处理用来组合这些传感器中数据并为设备的姿态和运动生成易用值所需的复杂数学。
本主题带您完成创建使用运动 API 的两个不同的应用程序。第一个应用程序非常简单,只是旋转屏幕上的一个三角形以响应设备在旋转方面的变化。第二个应用程序是一个增强现实的应用程序,它使用设备的相机和运动 API 允许用户标记设备周围空间中的点。
此示例使用的运动 API 需要所有支持的 Windows Phone 传感器,因此在没有必需传感器的设备上或设备模拟器上,这些示例应用程序都将正常失败且无法正常工作。
本节所述的应用程序使用 Polygon 对象的 RenderTransform 属性来旋转多边形。使用 AttitudeReading 类的 Yaw 属性的值更新 RenderTransform 的 Angle 属性。这将导致该三角形在设备旋转时跟着旋转。
创建基于运动的简单应用程序的步骤
在 Visual Studio 中,创建一个新的“Windows Phone 应用程序”项目。此模板在“Silverlight for Windows Phone”类别中。
该应用程序需要引用包含传感器 API 和 XNA Framework 的程序集。从“项目”菜单中,单击“添加引用...”,选择“Microsoft.Devices.Sensors”和“Microsoft.Xna.Framework”,然后单击“确定”。
在 MainPage.xaml 文件中,将以下 XAML 代码放置在名为“ContentPanel”的 Grid 元素中。
<StackPanel> <TextBlock Text="attitude" Style="{StaticResource PhoneTextLargeStyle}"/> <Grid Margin="12 0 12 0"> <TextBlock Height="30" HorizontalAlignment="Left" Name="yawTextBlock" Text="YAW: 000" VerticalAlignment="Top" Foreground="Red" FontSize="25" FontWeight="Bold"/> <TextBlock Height="30" HorizontalAlignment="Center" Name="pitchTextBlock" Text="PITCH: 000" VerticalAlignment="Top" Foreground="Green" FontSize="25" FontWeight="Bold"/> <TextBlock Height="30" HorizontalAlignment="Right" Name="rollTextBlock" Text="ROLL: 000" VerticalAlignment="Top" Foreground="Blue" FontSize="25" FontWeight="Bold"/> </Grid> <Grid Height="200"> <Polygon Name="yawtriangle" Points="205,135 240,50 275,135" Stroke="Red" StrokeThickness="2" > <Polygon.Fill> <SolidColorBrush Color="Red" Opacity="0.3"/> </Polygon.Fill> <Polygon.RenderTransform> <RotateTransform CenterX="240" CenterY="100"></RotateTransform> </Polygon.RenderTransform> </Polygon> <Polygon Name="pitchtriangle" Points="205,135 240,50 275,135" Stroke="Green" StrokeThickness="2" > <Polygon.Fill> <SolidColorBrush Color="Green" Opacity="0.3"/> </Polygon.Fill> <Polygon.RenderTransform> <RotateTransform CenterX="240" CenterY="100"></RotateTransform> </Polygon.RenderTransform> </Polygon> <Polygon Name="rolltriangle" Points="205,135 240,50 275,135" Stroke="Blue" StrokeThickness="2" > <Polygon.Fill> <SolidColorBrush Color="Blue" Opacity="0.3"/> </Polygon.Fill> <Polygon.RenderTransform> <RotateTransform CenterX="240" CenterY="100"></RotateTransform> </Polygon.RenderTransform> </Polygon> </Grid> <TextBlock Text="acceleration" Style="{StaticResource PhoneTextLargeStyle}"/> <Grid Margin="12 0 12 0"> <TextBlock Height="30" HorizontalAlignment="Left" Name="xTextBlock" Text="X: 000" VerticalAlignment="Top" Foreground="Red" FontSize="25" FontWeight="Bold"/> <TextBlock Height="30" HorizontalAlignment="Center" Name="yTextBlock" Text="Y: 000" VerticalAlignment="Top" Foreground="Green" FontSize="25" FontWeight="Bold"/> <TextBlock Height="30" HorizontalAlignment="Right" Name="zTextBlock" Text="Z: 000" VerticalAlignment="Top" Foreground="Blue" FontSize="25" FontWeight="Bold"/> </Grid> <Grid Height="300"> <Line x:Name="xLine" X1="240" Y1="150" X2="340" Y2="150" Stroke="Red" StrokeThickness="4"></Line> <Line x:Name="yLine" X1="240" Y1="150" X2="240" Y2="50" Stroke="Green" StrokeThickness="4"></Line> <Line x:Name="zLine" X1="240" Y1="150" X2="190" Y2="200" Stroke="Blue" StrokeThickness="4"></Line> </Grid> </StackPanel>
此代码创建三个 TextBlock 控件,以显示设备的 yaw、pitch 和 roll 度数值。另外,还创建三个三角形来以图形方式显示这些值。每个三角形中均添加了一个 RotateTransform,并且指定了要用作旋转中心的点。RotateTransform 的角度将在 C# 代码隐藏页面中进行设置,以根据手机的方向设置三角形动画。
接着,又使用了三个 TextBlock 控件来以数字方式显示设备沿每个轴的加速度。然后,添加了三条线来以图形方式显示加速度。
现在,打开 MainPage.xaml.cs 代码隐藏页面并向该页面顶部的其他 using 指令中添加传感器和 XNA Framework 命名空间的 using 指令。
using Microsoft.Devices.Sensors; using Microsoft.Xna.Framework;
在 MainPage 类定义的顶部声明一个类型为 Motion 的变量。
public partial class MainPage : PhoneApplicationPage { Motion motion;
接下来,重写 Page 类的 OnNavigatedTo(NavigationEventArgs) 方法。当用户导航到此页面时,将会调用此方法。在此方法中,检查 IsSupported 属性。并非所有设备都具有使用该功能所需的传感器,因此在使用此 API 之前,应该始终检查该值。之后,初始化 Motion 对象并将一个事件处理程序附加到 CurrentValueChanged 事件。以 TimeBetweenUpdates 参数中指定的间隔引发此事件。默认值为 2 毫秒。最后,通过调用 Start 方法开始获取数据。该方法可能会引发异常,因此将该方法放置在一个 try 块内。
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) { // Check to see whether the Motion API is supported on the device. if (! Motion.IsSupported) { MessageBox.Show("the Motion API is not supported on this device."); return; } // If the Motion object is null, initialize it and add a CurrentValueChanged // event handler. if (motion == null) { motion = new Motion(); motion.TimeBetweenUpdates = TimeSpan.FromMilliseconds(20); motion.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<MotionReading>>(motion_CurrentValueChanged); } // Try to start the Motion API. try { motion.Start(); } catch (Exception ex) { MessageBox.Show("unable to start the Motion API."); } }
定期引发当前值更改事件,以为应用程序提供新的传感器数据。从对应用程序 UI 没有访问权限的后台线程调用此事件处理程序。使用 BeginInvoke 在 UI 线程上调用 CurrentValueChanged。下面将定义该方法,该方法接受 MotionReading 对象作为参数。
void motion_CurrentValueChanged(object sender, SensorReadingEventArgs<MotionReading> e) { // This event arrives on a background thread. Use BeginInvoke to call // CurrentValueChanged on the UI thread. Dispatcher.BeginInvoke(() => CurrentValueChanged(e.SensorReading)); }
最后,创建 CurrentValueChanged 方法。此方法将 TextBlock 对象的 Text 属性分别设置为 yaw、pitch 和 roll 姿态读数值。接着,将每个三角形 RenderTransform 的 Angle 参数设置为根据相关的姿态值旋转每个三角形。XNA Framework 中的 MathHelper 类用于将弧度转换为度。然后,对加速度 TextBlock 对象进行了更新,以显示当前沿每个轴的加速度值。最后,对线条进行了更新来以图形方式说明设备的加速度。
private void CurrentValueChanged(MotionReading e) { // Check to see if the Motion data is valid. if (motion.IsDataValid) { // Show the numeric values for attitude. yawTextBlock.Text = "YAW: " + MathHelper.ToDegrees(e.Attitude.Yaw).ToString("0") + "°"; pitchTextBlock.Text = "PITCH: " + MathHelper.ToDegrees(e.Attitude.Pitch).ToString("0") + "°"; rollTextBlock.Text = "ROLL: " + MathHelper.ToDegrees(e.Attitude.Roll).ToString("0") + "°"; // Set the Angle of the triangle RenderTransforms to the attitude of the device. ((RotateTransform)yawtriangle.RenderTransform).Angle = MathHelper.ToDegrees(e.Attitude.Yaw); ((RotateTransform)pitchtriangle.RenderTransform).Angle = MathHelper.ToDegrees(e.Attitude.Pitch); ((RotateTransform)rolltriangle.RenderTransform).Angle = MathHelper.ToDegrees(e.Attitude.Roll); // Show the numeric values for acceleration. xTextBlock.Text = "X: " + e.DeviceAcceleration.X.ToString("0.00"); yTextBlock.Text = "Y: " + e.DeviceAcceleration.Y.ToString("0.00"); zTextBlock.Text = "Z: " + e.DeviceAcceleration.Z.ToString("0.00"); // Show the acceleration values graphically. xLine.X2 = xLine.X1 + e.DeviceAcceleration.X * 100; yLine.Y2 = yLine.Y1 - e.DeviceAcceleration.Y * 100; zLine.X2 = zLine.X1 - e.DeviceAcceleration.Z * 50; zLine.Y2 = zLine.Y1 + e.DeviceAcceleration.Z * 50; } }
确保您的设备连接到计算机并且通过在 Visual Studio 中按 F5 开始调试。将设备旋转到各种位置,并注意 yaw、pitch 和 roll 值如何根据设备的方向而改变。接着,将设备上下左右挥动,以查看加速度值如何改变。与加速度计 API 不同,读数中已过滤掉重力加速度,这样当设备静止时,加速度在所有轴上都为零。
增强现实是一个新术语,用于指在真实的环境中叠加很多改进(如图形或音频)的应用程序。此示例应用程序使用 PhotoCamera API 来显示来自设备相机的视频源。在视频源的顶部放置文本标签以标记空间中的点。使用运动 API 动态调整文本标签的位置,以便当设备方向更改时它们穿过相机取景器。这样便创建了标签固定到设备周围空间中的点的效果。该应用程序允许用户单击相机取景器中的点,然后用户便从屏幕空间转换到现实空间,并输入文本以标记该点。
以下过程中所述的该应用程序使用 XNA Framework 库进行将屏幕上的点投射到 3D 空间中以及返回的计算,但图形完全是在 Silverlight 中创建的。有关将这些框架一起使用的更多信息,请参阅如何:在 Windows Phone 应用程序中组合 Silverlight 和 XNA Framework。
创建基于 Silverlight 的增强现实应用程序的步骤
在 Visual Studio 中,创建一个新的“Windows Phone 应用程序”项目。此模板在“Silverlight for Windows Phone”类别中。
添加对包含传感器 API 和 XNA Framework 的程序集的引用。从“项目”菜单中,单击“添加引用...”,选择 Microsoft.Devices.Sensors、Microsoft.Xna.Framework 和 Microsoft.Xna.Framework.Graphics,然后单击“确定”。
采用 XAML 创建用户界面。该应用程序使用具有 VideoBrush 的 Rectangle 对象显示来自设备相机的视频流。这与主题如何为 Windows Phone 创建基本相机应用程序中所述的技术相同。
XAML 代码还创建一个 TextBox 控件,该控件允许用户指定将标记所选空间中的点的名称。将 TextBox 放置在 Canvas 控件中。在用户点按屏幕上的某些位置之前,Canvas 对象被隐藏。直到用户点按屏幕上的某些位置才显示,以便用户可以输入文本。当用户按 Enter 时,Canvas 再次隐藏。
在 MainPage.xaml 文件中,将以下 XAML 代码放置在名为“ContentPanel”的 Grid 元素中。
<Rectangle Width="640" Height="480" Canvas.ZIndex="1"> <Rectangle.Fill> <VideoBrush x:Name="viewfinderBrush" /> </Rectangle.Fill> <Rectangle.RenderTransform> <RotateTransform Angle="90" CenterX="240" CenterY="240"></RotateTransform> </Rectangle.RenderTransform> </Rectangle> <Canvas Name="TextBoxCanvas" Background="#BB000000" Canvas.ZIndex="99" Visibility="Collapsed"> <TextBlock Text="name this point" Margin="20,130,0,0"/> <TextBox Height="72" HorizontalAlignment="Left" Margin="8,160,0,0" Name="NameTextBox" VerticalAlignment="Top" Width="460" KeyUp="NameTextBox_KeyUp" /> </Canvas>
在 MainPage.xaml.cs 中,向文件顶部的现有 using 语句中添加以下 using 语句。Microsoft.Devices.Sensors 命名空间提供对 Motion API 的访问。Microsoft.Devices 用于访问 PhotoCamera API。该应用程序不使用 XNA Framework 呈现图形,但这些命名空间显示帮助器函数,这些函数将用于执行将屏幕中的点投射到现实空间以及返回所需的数学计算。最后的 using 指令用于消除 XNA Framework Matrix 类型的歧义,从而使代码的其余部分更易读。
using Microsoft.Devices.Sensors; using Microsoft.Devices; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Matrix = Microsoft.Xna.Framework.Matrix;
在 MainPage 类定义的顶部声明类成员变量。首先,声明 Motion 和 PhotoCamera 对象。然后,声明列表以存储 Vector3 对象(表示现实空间中点)和 TextBlock 对象(用于显示每个点的标签)。接下来,创建一个 Point 变量。该变量将存储用户在设备屏幕上触摸的点。最后的变量为 Viewport 对象以及一些用于将现实空间中的点投射到屏幕空间以及返回的 Matrix 对象。
public partial class MainPage : PhoneApplicationPage { Motion motion; PhotoCamera cam; List<TextBlock> textBlocks; List<Vector3> points; System.Windows.Point pointOnScreen; Viewport viewport; Matrix projection; Matrix view; Matrix attitude;
在页面的构造函数中,初始化 Point 和 TextBlock 对象的列表。
// Constructor public MainPage() { InitializeComponent(); // Initialize the list of TextBlock and Vector3 objects. textBlocks = new List<TextBlock>(); points = new List<Vector3>(); }
下一个方法是帮助器方法,该方法初始化 Viewport 和 Matrix 对象,这些对象用于将屏幕空间中的点转换为现实空间以及返回。Viewport 定义一个矩形,在该矩形上投射某个 3D 量。为了对该矩形进行初始化,传入呈现图面的宽度和高度 - 在此示例中,它为页面的宽度和高度。Viewport 结构显示方法 Project 和 Unproject,这些方法进行在屏幕空间和现实空间之间点的投射的数学计算。这些方法还需要一个投影矩阵和一个视图矩阵,也在此处对这两个矩阵进行初始化。
public void InitializeViewport() { // Initialize the viewport and matrixes for 3d projection. viewport = new Viewport(0, 0, (int)this.ActualWidth, (int)this.ActualHeight); float aspect = viewport.AspectRatio; projection = Matrix.CreatePerspectiveFieldOfView(1, aspect, 1, 12); view = Matrix.CreateLookAt(new Vector3(0, 0, 1), Vector3.Zero, Vector3.Up); }
在 OnNavigatedTo(NavigationEventArgs) 中,初始化相机并将相机设置为 XAML 中定义的 VideoBrush 的源。接下来,进行检查以确保设备上支持此 API 之后,初始化 Motion 对象。最后,注册 MouseLeftButtonUp 事件的事件处理程序。当用户触摸屏幕时将调用该处理程序。
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) { // Initialize the camera and set the video brush source. cam = new Microsoft.Devices.PhotoCamera(); viewfinderBrush.SetSource(cam); if (!Motion.IsSupported) { MessageBox.Show("the Motion API is not supported on this device."); return; } // If the Motion object is null, initialize it and add a CurrentValueChanged // event handler. if (motion == null) { motion = new Motion(); motion.TimeBetweenUpdates = TimeSpan.FromMilliseconds(20); motion.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<MotionReading>>(motion_CurrentValueChanged); } // Try to start the Motion API. try { motion.Start(); } catch (Exception ex) { MessageBox.Show("unable to start the Motion API."); } // Hook up the event handler for when the user taps the screen. this.MouseLeftButtonUp += new MouseButtonEventHandler(MainPage_MouseLeftButtonUp); base.OnNavigatedTo(e); }
在 MouseLeftButtonUp 事件处理程序中,检查包含 TextBox 控件的 Canvas 的 Visibility 属性。如果 Canvas 可见,则用户正在输入文本并且应该忽略此事件。如果 Canvas 不可见,则用户触摸的屏幕上的点已保存到 pointOnScreen 变量。查询 Motion 对象的 CurrentValue 属性以便获得设备的当前姿态,该内容保存在类变量 attitude 中。最后,使包含 TextBox 的 Canvas 可见并且将焦点给予 TextBox。
void MainPage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { // If the Canvas containing the TextBox is visible, ignore // this event. if (TextBoxCanvas.Visibility == Visibility.Visible) { return; } // Save the location where the user touched the screen. pointOnScreen = e.GetPosition(LayoutRoot); // Save the device attitude when the user touched the screen. attitude = motion.CurrentValue.Attitude.RotationMatrix; // Make the Canvas containing the TextBox visible and // give the TextBox focus. TextBoxCanvas.Visibility = Visibility.Visible; NameTextBox.Focus(); }
在后台线程上调用 Motion 类的 CurrentValueChanged 事件的事件处理程序。使用 BeginInvoke 在 UI 线程上调用另一个处理程序方法。
void motion_CurrentValueChanged(object sender, SensorReadingEventArgs<MotionReading> e) { // This event arrives on a background thread. Use BeginInvoke // to call a method on the UI thread. Dispatcher.BeginInvoke(() => CurrentValueChanged(e.SensorReading)); }
下面定义了 CurrentValueChanged 方法,在该方法中根据设备的当前姿态将 3D 空间中的点列表投射到屏幕空间。首先,检查 Viewport 结构,如有必要,调用上面定义的 InitializeViewport。接着,从 MotionReading 对象获取设备的姿态。运动 API 的坐标系与 XNA Framework 使用的坐标系不同,因此为了确保正确转换点,将姿态矩阵围绕 X 轴旋转 90 度。
接着,该方法循环通过应用程序的点列表中的每个点。对于每个点,创建一个表示现实空间到该点的偏移的 world 矩阵。该矩阵与之前定义的视图矩阵和投影矩阵一起传递到 Viewport 结构的 Project 方法。该方法返回一个 Vector3 对象,该对象的 X 和 Y 值为已投射的点的屏幕坐标。Z 值表示该点的深度。如果该值小于零或大于 1,则该点位于相机“后面”,因此该点的 TextBlock 被隐藏。如果该点位于相机的前面,则使用已投射的点的 X 和 Y 值创建一个 TranslateTransform 对象,然后将该对象分配给与该点关联的 TextBlock。
private void CurrentValueChanged(MotionReading reading) { // If the viewport width is 0, it needs to be initialized. if (viewport.Width == 0) { InitializeViewport(); } // Get the RotationMatrix from the MotionReading. // Rotate it 90 degrees around the X axis to put it in the XNA Framework coordinate system. Matrix attitude = Matrix.CreateRotationX(MathHelper.PiOver2) * reading.Attitude.RotationMatrix; // Loop through the points in the list. for (int i = 0; i < points.Count; i++) { // Create a World matrix for the point. Matrix world = Matrix.CreateWorld(points[i], new Vector3(0, 0, 1), new Vector3(0, 1, 0)); // Use Viewport.Project to project the point from 3D space into screen coordinates. Vector3 projected = viewport.Project(Vector3.Zero, projection, view, world * attitude); if (projected.Z > 1 || projected.Z < 0) { // If the point is outside of this range, it is behind the camera. // So hide the TextBlock for this point. textBlocks[i].Visibility = Visibility.Collapsed; } else { // Otherwise, show the TextBlock. textBlocks[i].Visibility = Visibility.Visible; // Create a TranslateTransform to position the TextBlock. // Offset by half of the TextBlock's RenderSize to center it on the point. TranslateTransform tt = new TranslateTransform(); tt.X = projected.X - (textBlocks[i].RenderSize.Width / 2); tt.Y = projected.Y - (textBlocks[i].RenderSize.Height / 2); textBlocks[i].RenderTransform = tt; } } }
接着,实现 KeyUp 事件处理程序,该处理程序分配给 XAML 中的 TextBox 控件。当用户在 TextBox 中输入文本时,调用该事件。对于该应用程序,该处理程序用于在用户按 Enter 键时添加新的点,因此如果按其他键,则代码的前面部分退出该处理程序。如果按 Enter,则 Canvas 再次被隐藏。接下来,该处理程序进行检查,查看该操作所需的任何对象是否为 null,如果为 null,则退出该方法。
之后,将之前在 MouseLeftButtonUp 事件处理程序中获得的点转换为 Viewport 结构的 Unproject 方法所需的格式。通过围绕 X 轴旋转 90 度将在 MouseLeftButtonUp 中获取的姿态值转换为 XNA 坐标空间。然后,调用 Unproject 将屏幕空间中的点转换为 3D 空间中的点。随后,对未投射的点进行标准化和缩放并且调用 AddPoint 帮助器方法向应用程序的列表中添加点以及伴随的 TextBox。
private void NameTextBox_KeyUp(object sender, KeyEventArgs e) { // If the key is not the Enter key, don't do anything. if (e.Key != Key.Enter) { return; } // When the TextBox loses focus. Hide the Canvas containing it. TextBoxCanvas.Visibility = Visibility.Collapsed; // If any of the objects we need are not present, exit the event handler. if (NameTextBox.Text == "" || pointOnScreen == null || motion == null) { return; } // Translate the point before projecting it. System.Windows.Point p = pointOnScreen; p.X = LayoutRoot.RenderSize.Width - p.X; p.Y = LayoutRoot.RenderSize.Height - p.Y; p.X *= .5; p.Y *= .5; // Use the attitude Matrix saved in the OnMouseLeftButtonUp handler. // Rotate it 90 degrees around the X axis to put it in the XNA Framework coordinate system. attitude = Matrix.CreateRotationX(MathHelper.PiOver2) * attitude; // Use Viewport.Unproject to translate the point on the screen to 3D space. Vector3 unprojected = viewport.Unproject(new Vector3((float)p.X, (float)p.Y, -.9f), projection, view, attitude); unprojected.Normalize(); unprojected *= -10; // Call the helper method to add this point. AddPoint(unprojected, NameTextBox.Text); // Clear the TextBox. NameTextBox.Text = ""; }
AddPoint 是一个帮助器方法,该方法获取 3D 空间中的一个点和一个字符串并将它们添加到 UI 以及应用程序的列表中。向列表中添加一个 Point 和 TextBox 之后,它们将显示在先前定义的 CurrentValueChanged 方法中。
private void AddPoint(Vector3 point, string name) { // Create a new TextBlock. Set the Canvas.ZIndexProperty to make sure // it appears above the camera rectangle. TextBlock textblock = new TextBlock(); textblock.Text = name; textblock.FontSize = 124; textblock.SetValue(Canvas.ZIndexProperty, 2); textblock.Visibility = Visibility.Collapsed; // Add the TextBlock to the LayoutRoot container. LayoutRoot.Children.Add(textblock); // Add the TextBlock and the point to the List collections. textBlocks.Add(textblock); points.Add(point); }
最后,在 PhoneApplicationPage 的 OnNavigatedFrom(NavigationEventArgs) 方法中,您应该调用 PhotoCamera 对象的 Dispose()()()(),以便在应用程序处于非活动状态时最大程度地减少相机的耗电量,并且加快相机关闭的速度。
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) { // Dispose camera to minimize power consumption and to expedite shutdown. cam.Dispose(); }
现在,您应该能够在您的设备上运行该应用程序了。使设备保持纵向。在相机取景器中查找对象,如门或窗户。触摸该对象以显示命名文本框。为空间中的点键入一个名称,然后按 Enter 键。您应该能够来回旋转设备并且看到标签始终位于空间中的同一点上。请记住,该应用程序不使用通过空间的设备运动,只是改变它的方向,因此如果您移动设备的幅度较大,则标记的点将不会正确排列。
可以向应用程序中添加以下帮助器方法,以相对于设备传感器标记 3D 空间的前、后、左、右、上、下。这将对查看应用程序的工作方式非常有帮助。
private void AddDirectionPoints() { AddPoint(new Vector3(0, 0, -10), "front"); AddPoint(new Vector3(0, 0, 10) , "back"); AddPoint(new Vector3(10, 0, 0) , "right"); AddPoint(new Vector3(-10, 0, 0) , "left"); AddPoint(new Vector3(0, 10, 0) , "top"); AddPoint(new Vector3(0, -10, 0), "bottom"); }