エクスポート (0) 印刷
すべて展開

方法: Windows Phone の Combined Motion API を使用する

2012/02/09

Motion API は、入力メカニズムとして、デバイスの向きと空間内での動きを使用する Windows Phone アプリケーションを作成する場合に役に立ちます。デバイスの CompassGyroscope、および Accelerometer センサーから未処理のセンサー データを取得するために使用できる API がありますが、Motion API は、これらのセンサーからのデータを組み合わせるために必要な複雑な数値演算を処理し、デバイスの姿勢やモーションの使いやすい値を生成します。

このトピックでは、Motion API を使用する 2 種類のアプリケーションの作成について説明します。1 つ目のアプリケーションはきわめて簡単で、デバイスの回転の変化に対応して、画面上の三角形を回転させるだけです。2 つ目のアプリケーションは、デバイスのカメラと Motion API を使用して、ユーザーがデバイスの周囲の空間の点にラベル付けできる拡張現実アプリケーションです。

このサンプルで使用する Motion API では、サポートされているすべての Windows Phone センサーを必要とするため、必要なセンサーがないデバイスやデバイス エミュレーターでは、これらのサンプル アプリケーションは失敗します。つまり、正常に動作しません。

このセクションで説明しているアプリケーションでは、Polygon オブジェクトの RenderTransform プロパティを使用して、ポリゴンを回転します。RenderTransformAngle プロパティを AttitudeReading クラスの Yaw プロパティの値によって更新します。これにより、デバイスの回転に合わせて三角形を回転させます。

簡単なモーションベースのアプリケーションを作成するには

  1. Visual Studio で、新しい Windows Phone アプリケーション プロジェクトを作成します。このテンプレートは、Silverlight for Windows Phone カテゴリにあります。

  2. このアプリケーションでは、センサー API と XNA Framework を含むアセンブリへの参照が必要です。[プロジェクト] メニューの [参照の追加] をクリックし、[Microsoft.Devices.Sensors] と [Microsoft.Xna.Framework] を選択し、[OK] をクリックします。

  3. MainPage.xaml ファイルで、"ContentPanel" というの名前の Grid 要素に次の XAML コードを配置します。

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

    このコードにより 3 つの TextBlock コントロールが作成され、デバイスのヨー、ピッチ、およびロールの数値が角度で表示されます。また、3 つの三角形が作成され、値をグラフィカルに表示します。各三角形に RotateTransform を追加し、回転の中心として使用する点を指定します。C# 分離コード ページでは RotateTransform の角度が設定され、電話の向きに応じて三角形をアニメーション化します。

    次に、TextBlock コントロールが 3 つ追加で使用され、各軸に沿ったデバイスの加速度を数値で表示します。また、3 本の線が追加され、加速度をグラフィカルに表示します。

  4. MainPage.xaml.cs 分離コード ページを開き、センサーと XNA Framework 名前空間の using ディレクティブを、ページの先頭にある他の using ディレクティブに追加します。

    using Microsoft.Devices.Sensors;
    using Microsoft.Xna.Framework;
    
    
  5. MainPage クラス定義の先頭に Motion 型の変数を宣言します。

    public partial class MainPage : PhoneApplicationPage
    {
      Motion motion;
    
    
  6. 次に、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.");
      }
    }
    
    
  7. 現在の値の変更イベントが定期的に発生し、アプリケーションに新しいセンサー データが提供されます。このイベント ハンドラーは、アプリケーション 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));
    }
    
    
  8. 最後に CurrentValueChanged メソッドを作成します。このメソッドにより TextBlock オブジェクトの Text プロパティが、ヨー、ピッチ、およびロールの姿勢測定値に設定されます。続いて、関連する姿勢値に応じて各三角形を回転させるように、各三角形の RenderTransformAngle パラメーターが設定されます。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;
      }
    }
    
    
  9. デバイスがコンピューターに接続されていることを確認して、Visual Studio の F5 キーを押してデバッグを開始します。デバイスをさまざまな位置に回転させて、デバイスの向きに応じてヨー、ピッチ、およびロールの値がどのように変化するかに着目します。続いて、デバイスを上下左右に揺らし、加速度の値がどのように変化するかを確認します。加速度計 API とは異なり、デバイスが静止している場合に加速度がすべての軸でゼロになるよう、重力の加速度は読み取りから除外されます。

拡張現実は、現実に目に見えるものの上に、グラフィックやオーディオなどの拡張機能を重ねるアプリケーションを指す用語です。このアプリケーションの例では、PhotoCamera API を使用して、デバイス カメラからのビデオ フィードを表示します。ビデオ フィードの上部にテキスト ラベルを配置し、空間内の点にラベル付けします。Motion API を使用して、デバイスの向きの変化に合わせて、テキスト ラベルがカメラのファインダーを動き回るように、テキスト ラベルを動的に配置します。これにより、ラベルがデバイスの周囲の空間内の点に固定される効果を作成します。このアプリケーションでは、ユーザーがカメラのファインダー内の点をクリックすると、点が画面空間からワールド空間に変換され、テキストを入力して、点にラベル付けすることができます。

次の手順で説明するこのアプリケーションでは、画面上の点を 3 次元空間および背面に投影する計算に XNA Framework ライブラリを使用していますが、グラフィックはすべて Silverlight で作成します。これらのフレームワークを組み合わせて使用する詳細については、「方法: Windows Phone アプリケーションで Silverlight と XNA Framework を組み合わせる」を参照してください。

Silverlight ベースの拡張現実アプリケーションを作成するには

  1. Visual Studio で、新しい Windows Phone アプリケーション プロジェクトを作成します。このテンプレートは、Silverlight for Windows Phone カテゴリにあります。

  2. センサー API と XNA Framework を含むアセンブリへの参照を追加します。[プロジェクト] メニューの [参照の追加] をクリックし、[Microsoft.Devices.Sensors]、[Microsoft.Xna.Framework]、および [Microsoft.Xna.Framework.Graphics] を選択し、[OK] をクリックします。

  3. XAML でユーザー インターフェイスを作成します。このアプリケーションでは、Rectangle オブジェクトと VideoBrush を使用して、デバイスのカメラからのビデオ ストリームを表示します。これは、トピック「方法: Windows Phone 用の基本的なカメラ アプリケーションを作成する」で説明している手法と同じです。

    この XAML コードは、ユーザーが選択した空間内の点にラベル付けする名前を指定することができる TextBox コントロールも作成します。TextBoxCanvas コントロール内に配置します。Canvas オブジェクトは、ユーザーが画面上のどこかをタップするまで非表示です。その時点で、表示されるため、ユーザーはテキストを入力できます。ユーザーが Enter キーを押すと、Canvas が再度非表示になります。

    MainPage.xaml ファイルで、"ContentPanel" というの名前の Grid 要素に次の XAML コードを配置します。

    <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>
    
    
  4. 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;
    
    
  5. 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;
    
    
  6. ページ コンストラクターで、Point オブジェクトと TextBlock オブジェクトのリストを初期化します。

    // Constructor
    public MainPage()
    {
      InitializeComponent();
    
      // Initialize the list of TextBlock and Vector3 objects.
      textBlocks = new List<TextBlock>();
      points = new List<Vector3>();
    }
    
    
  7. 次のメソッドは、画面空間からワールド空間および背面に点を変換するために使用する Viewport オブジェクトと Matrix オブジェクトを初期化するヘルパー メソッドです。Viewport は 3 次元ボリュームを投影する三角形を定義します。三角形を初期化するには、レンダリング サーフェイスの幅と高さを渡します。この例ではこれはページの幅と高さになります。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);
    }
    
    
  8. 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);
    }
    
    
  9. MouseLeftButtonUp イベント ハンドラーで、TextBox コントロールを含む CanvasVisibility プロパティをチェックします。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();
    }
    
    
  10. バックグランド スレッドで、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));
    }
    
    
  11. 下に定義する CurrentValueChanged メソッドでは、デバイスの現在の姿勢に従って、3 次元空間の点のリストを画面空間に投影します。最初に Viewport 構造体をチェックし、必要に応じて上に定義した InitializeViewport を呼び出します。次に、デバイスの姿勢を MotionReading オブジェクトから取得します。Motion API の座標系は、XNA Framework で使われている座標系と異なるため、点が正しく変換されていることを確認するために、姿勢マトリックスを X 軸で 90 度回転させます。

    次に、メソッドを、アプリケーションの点のリストの各点でループします。各点ごとに、ワールド空間内の点までのオフセットを表すワールド マトリックスを作成します。このマトリックスを、先に定義したビュー マトリックスと投影マトリックスと一緒に Viewport 構造体の Project メソッドに渡します。このメソッドは、Vector3 オブジェクトを返します。その X 値と Y 値は投影された点の画面座標です。Z 値は、点の深さを示します。この値が 0 未満か 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;
        }
      }
    }
    
    
  12. 次に、XAML で、TextBox コントロールに割り当てられた KeyUp イベント ハンドラーを実装します。このイベントは、ユーザーが TextBox にテキストを入力したときに呼び出されます。このアプリケーションでは、ユーザーが Enter キーを押したときに新しい点を追加するためにハンドラーが使用されます。他のキーが押された場合は、コードの最初の部分でハンドラーを終了します。Enter キーが押された場合は、再度 Canvas が非表示になります。次に、ハンドラーは、この操作に必要なすべてのオブジェクトが null でないことを確認し、null である場合はメソッドを終了します。

    さらに、MouseLeftButtonUp イベント ハンドラーで以前に取得された点を、Viewport 構造体 Unproject メソッドで必要な形式に変換します。MouseLeftButtonUp で取得した姿勢値は、X 軸で 90 度回転させて、XNA 座標空間に変換します。次に、Unproject を呼び出して、画面空間内の点を 3 次元空間内の点に変換します。投影されない点は正規化し、スケーリングして、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 = "";
    }
    
    
  13. AddPoint は、3 次元空間内の点と文字列を取得し、それらを UI とアプリケーション リストに追加するヘルパー メソッドです。PointTextBox をリストに追加したら、それらを以前に定義した 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);
    }
    
    
  14. 最後に、PhoneApplicationPageOnNavigatedFrom(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 キーを押します。デバイスを回転させると、ラベルが空間内の同じ点の上に常に表示されることがわかります。このアプリケーションでは、空間全体のデバイスの動きを使用せず、その向きのみを使用するため、デバイスを大きく動かした場合に、ラベル付けされた点が正しく整列されません。

次のヘルパー メソッドをアプリケーションに追加して、デバイス センサーに相対的に 3 次元空間の前、後、左、右、上、下にラベル付けすることができます。これは、アプリケーションの動作を視覚化する場合に役に立ちます。

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

表示:
© 2014 Microsoft