Mango Teapot ③ 加速度センサーとモーション

Mango Teapot は Windows Phone 7.5 の AR アプリケーション サンプルです。ソースコードは mangoteapot.codeplex.com にあります。

  1. Silverlight と XNA によるグラフィックス デバイスの共有
  2. Teapot クラス
  3. 加速度センサーとモーション
  4. カメラ

Mango-Teapot_thumb2_thumb1

Windows Phone 7.5 (“Mango”) では、Windows Phone に搭載されているセンサー、加速度計・コンパス(方位計)・ジャイロスコープからデータを取得する Accelerometer, Compass, Gyroscope クラスが用意されています。ジャイロスコープはオプションなので搭載されていないデバイスもあります(ジャイロスコープ搭載デバイスのほうが少ない?)。

個々のセンサーから生データを取得して自分で必要なデータに加工してもよいのですが、これらのセンサーをまとめて加工済みのデータを取得できる Motion クラスが用意されています。例えば、この Mango Teapot のようにデバイスの姿勢を取得して3Dオブジェクトの姿勢を制御したいとき、加速度計から重力方向をコンパスから方位をそれぞれ取得して行列を計算するより、Motion からヨー・ピッチ・ローの各角度、あるいは回転行列を取得したほうが簡単です。

MSDN のサンプルは Silverlight ベースなので定期的にコールバックでセンサーのデータを取得していますが、XNA なら OnUpdateで CurrentValue プロパティを使って現在の値を取得するほうが簡単です。CurrentValue については田中達彦さんに教えてもらいました、感謝。回転行列が一発で取得できる Motion.CurrentValue.Attitude.RotationMatrix って素敵ですよね。

private void OnUpdate(object sender, GameTimerEventArgs e)

{

  // TODO: Add your update logic here

  if (useGravity)

  {

    if (useMotion)

    {

      teapot.World =
Matrix.CreateFromYawPitchRoll(0,MathHelper.PiOver2, 0) *
motion.CurrentValue.Attitude.RotationMatrix;

    }

    …

}

でも、今の Mango(Beta 2)ではまだコンパスのドライバーが入っていないらしく、Motion.IsSupported が fail を返します。そこでこの Mango Teapot では、加速度計だけしか動かなくても、重力方向だけで Teapot の姿勢を変更しました。この場合は上下だけ正しい状態を維持します(つまりティーポットのふたは必ず上を向くので、お茶はこぼれない ウインク)。

// Motion

if (Motion.IsSupported)

{

  motion = new Motion();

  useMotion = true;

}

// Accelerometer

else if (Accelerometer.IsSupported)

{

  accelSensor = new Accelerometer();

  useAccelerometer = true;

}

// neither

else

{

  button1.Visibility = Visibility.Collapsed;

}

デフォルトはタッチとジェスチャーで回転・拡大縮小でき、[Gravity] ボタンで Motion が使えれば  Motion を、使えなければ Accelerometer を使うようにします。このボタンにはもちろん Silverlight を使います。XNA と Silverlight のハイブリッド プログラムは快適です。

image

// Silverlight ボタン用のコールバック

private void button1_Click(object sender, RoutedEventArgs e)

{

  Button b = sender as Button;

  string content = b.Content as string;

  if (content == "Gravity")

  {

    useGravity = true;

    b.Content = "Touch";

    if (useMotion) motion.Start();

    else if (useAccelerometer) accelSensor.Start();

  }

  else

  {

    useGravity = false;

    b.Content = "Gravity";

    if (useMotion) motion.Stop();

    else if (useAccelerometer) accelSensor.Stop();

  }

}