transform と deviceorientation における回転の表現 (HTML)

CSS の transform プロパティと JavaScript の deviceorientation イベントではともに 3D の回転状態 (姿勢、傾き) が登場しますが、
その扱い方に差があるため検証しました。
deviceorientation は、デバイスのジャイロ センサーが回転状態を通知することで発生するイベントです。

HTML の 3 次元座標系では、2 次元スクリーン座標系の x 軸および y 軸に加えて、スクリーンに垂直な z 軸が存在します。
デバイス (スマートフォンなど) を水平に持ち、北を向いた状態を基準に考えます。

このとき、CSS の transform プロパティと JavaScript の deviceorientation イベントにおける、
回転に関する性質の違いを下の表にまとめました。例えば z 軸を中心とする回転の角度は、現在は北を向いていたとしたら、
transform では東側 (時計回り) を向くと正、deviceorientation では西側を向くと正になります。

  transform deviceorientation
座標系 左手系 右手系
x 軸 右が正 右が正
y 軸 下 (手前) が正 上 (奥) が正
z 軸 (画面が水平のとき) 鉛直の上が正 鉛直の上が正
回転角度 回転軸の正方向に左ねじを進める場合が正 回転軸の正方向に右ねじを進める場合が正

 

検証のため、CSS の transform プロパティと JavaScript の deviceorientation イベントを利用して、
デバイスの回転状態を画面内の立方体オブジェクトに同期させるサンプルを作成しました。

DeviceOrientation

ジャイロ センサーを搭載した端末であれば、こちらのテストページで確認できます。
HTML のソースは以下の通りです。

以下は、各技術についての説明です。

transform プロパティ

transform プロパティで rotateX などを利用して回転状態を指定する場合、次に示すように複数の回転を重ね合わせることができます。

CSS:
transform: rotateX(45deg) rotateY(30deg) rotateZ(60deg);

JavaScript:
element.style.transform = "rotateX(45deg) rotateY(30deg) rotateZ(60deg)";

ただし、座標系ごと回転させながら左から順に適用します (オイラー角)。
これは、以前に 3D における回転の表現と相互変換で書いた通り、元の座標系のまま右から順に適用する、と考えても同じです。

以下に rotateX(45deg)rotateY(45deg) を組み合わせた例を載せておきます。

初期状態

rotateX(45deg) (左)        rotateX(45deg) rotateY(45deg) (右)

rotateY(45deg) (左)        rotateY(45deg) rotateX(45deg) (右)

deviceorientation イベント

window.addEventListenerdeviceorientation に対するイベントリスナーを登録します。
デバイスの回転状態が変化すると、イベントリスナーが呼び出されます。

引数の alpha, beta, gamma はそれぞれ z 軸、x 軸、y 軸を中心とした回転の角度を表し、
座標系ごと回転させながらこの順に重ね合わせたものが回転状態を表します。
それぞれの値の範囲は次の通りです。

  • z 軸: 0 ≦ alpha < 360
    • 北を向いたとき、alpha = 0
  • x 軸: -180 ≦ beta < 180
  • y 軸: -90 ≦ gamma < 90

 

回転状態の同期

以上から、デバイスの回転状態を画面内のオブジェクトに同期させるには次のようにします。

cubeEl.style.transform = `rotateZ(${-e.alpha}deg) rotateX(${-e.beta}deg) rotateY(${e.gamma}deg)`;

正負の符号に注意します。
結果として、z 軸および x 軸における回転角度の正負は異なり、y 軸では同じになります。

 

作成したサンプル
参照
transform
deviceorientation
広告

Roslyn の構文解析を使ってデバッガーを自作する

// C# Advent Calendar 2018 の 23 日目の記事です。

デバッガーのようなものを自作してみました。

動機

  • 普段は Visual Studio を使っているが、デバッグ時に手動でステップ実行するのが面倒
    • ループなどでステップ数が多い場合
    • 分岐の様子や変数の状態を軽くチェックしたい場合

解決案

  • ステップの時間間隔だけを指定して、デバッガーを自動で実行させる
    • 変数の一覧が表示される
    • 時間間隔をリアルタイムで調節できる
  • .NET Compiler Platform (Roslyn) の構文解析の機能を使い、各ステップの間にデバッグ用のコードを差し込めば実現できそう

結果

というわけで、WPF でプロトタイプ「Tick-tack Debugger」を作ってみた結果、このようになりました。
例として、ニュートン法で平方根を求めています。 (クリックで拡大)

 

解説

以下は概略の技術解説です。
WPF アプリを作成する前に、まず .NET Framework 上のコンソール アプリで実験してみます。
C# の構文解析を使うには、NuGet で Microsoft.CodeAnalysis.CSharp をインストールします。

デバッグ対象となるソースコードにデバッグ コードを挿入し、それを動的にコンパイルして実行する、という方針です。
コンソール アプリのソースコードを以下に示します (全体のソリューションは SyntaxTreeSample にあります)。

SyntaxHelper クラスでは、デバッグ対象の C# ソースコードを構文ツリー (SyntaxTree) に変換して走査し、
各ステートメントの前にデバッグ用のコード行を挿入していきます。

CSharpSyntaxTree.ParseText メソッドを使うことで、ソースコードを構文ツリーに変換できます。
また、メソッド・ステートメント・式など、すべてのノードを表す親クラスは SyntaxNode クラスであり、

  • Parent プロパティ: 親
  • Ancestors メソッド: 祖先
  • ChildNodes メソッド: 子
  • DescendantNodes メソッド: 子孫

が存在することを知っておけば、だいたいの探索ができるでしょう。

この他に、デバッグ用のコードから呼び出されるメソッドを定義するクラス ライブラリとして DebuggerLib を作成しています。
各ステートメントの位置、およびその直前で存在する変数とその値を通知するために、このライブラリを経由させます。

Program クラスでは、生成されたデバッグ用のソースコードをファイルに保存したら、
System.CodeDom.Compiler 名前空間の CodeDomProvider を使ってこれをコンパイルし、
そのエントリ ポイント (Main メソッド) を呼び出します。
また、デバッグ コードが実行されたときのイベントハンドラーを登録しておき、
Thread.Sleep メソッドを使って、指定した時間だけ停止させます。

これで、デバッグ対象の元のソースコードが次の Program.cs だとすると、
デバッグ用のソースコードとして下の Program.g.cs が生成されます。

作成したコンソール アプリを実行すると、次の図のようになります (時間間隔は 0.3 秒)。

 

以上をもとに、WPF アプリでデバッグ ツールを作成しました。
左側の C# ソースコードの部分は TextBox で、編集もできます。
デバッグ実行時は、各ステートメントを選択状態にすることでハイライトしています。
右側の変数一覧が表示される部分は DataGrid です。

(図は円周率を求める例)

今回は上記の方法でプロトタイプを作ってみましたが、
デバッグ コードの挿入やコンパイルに関しては、よりスマートな方法があるのではないかと思います。

注意点
  • 考えられうるすべてのステートメントには対応できていません。また、Main メソッドしか構文解析していません。
  • コンパイル時に生成されるアセンブリ (EXE) は、%TEMP% フォルダー (ユーザーの AppData\Local\Temp) に保存されていきます。
  • TextBox で、IsInactiveSelectionHighlightEnabled を True に設定しても利かないことがあります。
    また、選択状態のハイライトがずれることがあります。
    RichTextBox で Run などを使うのがよいかもしれません。

 

作成したサンプル
バージョン情報
参照

Leap Motion で手の回転状態を取得する

Leap Motion Controller の公式 SDK では、手の回転の状態をオイラー角で取得できるようになっています。
具体的には、Hand.Direction (Vector オブジェクト) の Yaw, Pitch, Roll プロパティが用意されています。
ただし、Hand クラスの説明を参照すると、 ロールについては Direction.Roll ではなく PalmNormal.Roll を使うように書かれています。

float pitch = hand.Direction.Pitch;
float yaw = hand.Direction.Yaw;
float roll = hand.PalmNormal.Roll;

しかし、これらの値を使って実装してみても、期待通りの動作にはなりません。

そこで、前回の 3D における回転の表現と相互変換の内容をもとに、手の回転の状態を取得する機能を自作しました。

Hand.Direction と Hand.PalmNormal はともに長さ 1 で直交しているため、
これらをそれぞれ (0, 0, -1) と (0, -1, 0) の回転後のベクトルと見なして、
前回作成した Rotation3DHelper クラスを利用してオイラー角を求めれば OK です。

全体のソースコードは HandRotationLeap (GitHub) にあります。
このサンプルでは、手とさいころの回転の状態を同期させています。

Hand Rotation by Leap Motion Controller

前回: 3D における回転の表現と相互変換

バージョン情報
.NET Framework 4.5
Leap Motion SDK 2.3.1

参照
Hand クラス

3D における回転の表現と相互変換

以前に投稿した WPF で 3D オブジェクトを回転させるではオブジェクトの回転の状態を行列で表していましたが、
3 次元空間における回転を表現する方法は、次のように何通りか考えられます。
なお、原点を中心に回転させるものとし、回転角度は、回転軸を表すベクトルの方向に右ねじを押し込む場合を正とします。

  1. 行列
    • 3 次正方行列。とくに、回転を表すものは直交行列であり、P^{-1} = {}^t P が成り立つ
    • WPF では Matrix3D 構造体
  2. 四元数 (クォータニオン)
  3. オイラー角 (ロール、ピッチ、ヨー)
    • 任意の回転を 3 回の単純な回転の合成により表す。詳細の定義はロール・ピッチ・ヨー ― Kinectで学ぶ数学を参照
    • 座標系ごと回転させる
    • ロール、ピッチ、ヨーをそれぞれどの軸に対応させるか、どの順番で作用させるかで結果が変わってしまうため注意が必要
  4. 特定の 2 点の回転前後の座標
    • とくに、直交する 2 つのベクトルを用いるとよい

3D のプログラミングをしていると、API によってどの表現を利用するかが異なることがあります。
以下では、これらの表現を互いに変換する方法について考えます。

■ 行列 → 2 点の座標

任意のベクトルに対して行列を作用させれば回転後のベクトルが求められます。
WPF では演算子 * が用意されています。

■ 四元数 → 行列

四元数の成分から行列の各成分を計算できます (詳細は四元数による回転行列の表現を参照)。
WPF では Matrix3D.Rotate メソッドとして用意されています。

■ オイラー角 → 行列または四元数

ここでは、ヨーは y 軸、ピッチは x 軸、ロールは z 軸のまわりの回転を表し、この順に適用されるとします。
(元の座標系における) ヨー、ピッチ、ロールを表す回転行列をそれぞれ R_y, R_p, R_r とし、それぞれの回転角度を \theta_y, \theta_p, \theta_r とします。
すなわち、

R_y = \left( \begin{array}{ccc} \cos \theta_y & 0 & \sin \theta_y \\ 0 & 1 & 0 \\ -\sin \theta_y & 0 & \cos \theta_y \end{array} \right), R_p = \left( \begin{array}{ccc} 1 & 0 & 0 \\ 0 & \cos \theta_p & -\sin \theta_p \\ 0 & \sin \theta_p & \cos \theta_p \end{array} \right), R_r = \left( \begin{array}{ccc} \cos \theta_r & -\sin \theta_r & 0 \\ \sin \theta_r & \cos \theta_r & 0 \\ 0 & 0 & 1 \end{array} \right)

このとき、座標系ごとヨー、ピッチ、ロールの順に適用した回転は、元の座標系で R_y R_p R_r で表されます (適用の順序が逆になる)。
証明は次のようにできますが、実際の回転をイメージするとわかりやすいと思います。

証明

ベクトル {\bf x} にヨーを作用させると、
R_y {\bf x}
次に、これにピッチを作用させるには、いったん座標系を戻して R_p を作用させるから、
R_y R_p R_y^{-1} \cdot R_y {\bf x} = R_y R_p {\bf x}
同様に、これにロールを作用させると、
(R_y R_p) R_r (R_y R_p)^{-1} \cdot R_y R_p {\bf x} = R_y R_p R_r {\bf x}
(証明終)

WPF での実際の演算では四元数を使うとよいでしょう。

■ 2 点の座標 → オイラー角

ここでは、2 点として {\bf e}_z = (0, 0, 1), {\bf e}_y = (0, 1, 0) を選びます。
これらの回転後のベクトルがそれぞれ {\bf u}, {\bf t} で与えられたとすると、オイラー角は以下の手続きにより求められます。

まず、{\bf u} の x 要素と z 要素の比はピッチおよびロールの影響を受けないから、
\tan \theta_y = \dfrac{{\bf u}_x}{{\bf u}_z}
により、\theta_y, R_y が決まる。

ピッチおよびロールの決め方から、
\tan \theta_p = \dfrac{- (R_p {\bf e}_z)_y}{(R_p {\bf e}_z)_z}, \tan \theta_r = \dfrac{- (R_r {\bf e}_y)_x}{(R_r {\bf e}_y)_y}

また、前項と同様に考えて、{\bf u} = R_y R_p {\bf e}_z, {\bf t} = R_y R_p R_r {\bf e}_y であるから、
R_p {\bf e}_z = R_y^{-1} {\bf u}, R_r {\bf e}_y = R_p^{-1} R_y^{-1} {\bf t}

したがって、\theta_p, \theta_r も順に決まる。
なお、この決め方の場合、回転角度の範囲は、
- \pi < \theta_y \le \pi, - \dfrac{\pi}{2} \le \theta_p \le \dfrac{\pi}{2}, - \pi < \theta_r \le \pi
となる。
(終)

arctan を求めるには、Math.Atan2 メソッドを使うとよいでしょう。

以上により、行列 → 2 点の座標 → オイラー角 → 四元数 → 行列と変換する方法が与えられたので、
回転の表現を相互に変換できるようになります。

では、これらを実装してみます。
ベクトル、行列、四元数を扱うための System.Numerics.Vectors という、WPF のライブラリよりも高機能なライブラリもあるのですが、
今回は WPF のライブラリのみを利用して実装したいと思います。

全体のソースコードは RotationTest (GitHub) にあります。
これらのメソッドを呼び出すテストが付いています。

前回: WPF で 3D オブジェクトを回転させる
次回: Leap Motion で手の回転状態を取得する

バージョン情報
.NET Framework 4.5

参照

WPF で 3D オブジェクトを回転させる

前回の WPF で 3D オブジェクトを表示するに引き続いて、今回は 3D オブジェクトを回転させます。
図のようにボタンを配置して、6 方向の回転ができるように実装します。

次のようにコードを追加・変更します。

ボタンとして RepeatButton を配置しています。
RepeatButton は、押したままにしておけば断続的に Click イベントが発生します。
また回転の状態を表すために、ModelVisual3D.Transform の中で MatrixTransform3D を使います。

回転には、回転軸と回転角度が必要です。

  • 回転軸は、ベクトルで表されます。
  • 回転角度は、回転軸の方向に右ねじを押し込む場合を正とします。

6 つのボタンはそれぞれ、x, y, z 軸を回転軸とした正方向または負方向の回転を表します。
1 回の Click イベントにつき 5° ずつ回転させています。
なお、カメラは z 軸上の正の位置から原点方向を見下ろし、右側が x 軸の正、上側が y 軸の正を表しています。

Click イベントハンドラーの中で、Matrix3D.Rotate メソッドを呼び出すことでオブジェクトを回転させます。
引数には、回転軸と回転角度を表す Quaternion (四元数) を指定します。
このように、回転軸と回転角度がわかっている場合は比較的簡単に実装ができます。

下図は、最初の状態から x 軸のまわりに -60° 回転させたところです。

全体のソースコードは DiceRotationWpf (GitHub) にあります。
マウスまたはタッチのドラッグ操作でも回転できるようになっています。

Dice Rotation

 

前回: WPF で 3D オブジェクトを表示する
次回: 3D における回転の表現と相互変換

作成したサンプル
DiceRotationWpf (GitHub)

バージョン情報
.NET Framework 4.5

参照
3-D グラフィックスの概要
3-D 変換の概要

WPF で 3D オブジェクトを表示する

WPF の 3D グラフィックスの機能を使って、3D オブジェクトを表示する方法を示します。
XAML でさいころのような立方体を描画することを目指します。

Dice (XAML)

先に XAML を示します。この後に簡単な説明が続きます。

 

まず、Viewport3D を配置します。
3D オブジェクトを描画するには次の 3 種類のものが必要で、これらを Viewport3D に設定します。

カメラ
Viewport3D.Camera プロパティに指定します。PerspectiveCamera を使うと、遠近感が出ます。
既定では LookDirection="0,0,-1" UpDirection="0,1,0" となっており、
つまり z 軸の逆方向を向いており、y 軸の正方向が上になっています。

照明
通常のオブジェクトと同様に、Viewport3D.Children プロパティにモデルとして追加します。
AmbientLight を使うと、影がなく、一様な明るさでオブジェクトが表示されます。

オブジェクト
主に ModelVisual3D として定義し、Viewport3D.Children プロパティに追加します。

以下では、ModelVisual3D の設定について書いていきます。
GeometryModel3D でオブジェクトを定義し、ModelVisual3D.Content プロパティにそれを指定します。
複数のオブジェクトをグループ化するには、Model3DGroup を使います。
GeometryModel3D には、マテリアルとジオメトリを指定します。

マテリアル

GeometryModel3D.Material プロパティで、何を表示するかを指定します。
ここでは、さいころの面を描くために 2D の TextBlock を用意しておき、それをテクスチャとして指定しています。
2D の Brush をテクスチャとして利用するには、DiffuseMaterial を使います。

また、BackMaterial プロパティに、Material プロパティと同じものを指定すれば、裏面には表面を反転したものが表示されます。
つまり、「3」の裏面は「ε」のようになります。

ジオメトリ

GeometryModel3D.Geometry プロパティで、どこに表示するかを指定します。
具体的には、MeshGeometry3D を利用して、次のものを指定します。

点の集合 (Positions プロパティ)
メッシュを構成する三角形に分割した場合に、頂点となる点の集合を指定します。

三角形の集合 (TriangleIndices プロパティ)
Positions プロパティで指定した点に0からインデックスを付けて、メッシュを構成する三角形の頂点を列挙します。
三角形の頂点の順番は、表側から見て反時計回りになるように指定します。

テクスチャの位置 (TextureCoordinates プロパティ)
Positions プロパティで指定した各点が、マテリアルの中のどの点に対応するかを指定します。
マテリアルは 2D なので、左上が (0, 0)、右下が (1, 1) です。

例えば、

<MeshGeometry3D Positions="-1,-1,1 1,-1,1 1,1,1 -1,1,1" TriangleIndices="0,1,2 0,2,3" TextureCoordinates="0,1 1,1 1,0 0,0"/>

とした場合は、P0(-1, -1, 1), P1(1, -1, 1), P2(1, 1, 1), P3(-1, 1, 1) の 4 点があり、
メッシュは P0P1P2 と P0P2P3 の 2 つの三角形で構成され、
P0 が左下、P1 が右下、P2 が右上、P3 が左上となるようにテクスチャを描画するという意味になります。
なお、これらの値を XAML 上で列挙するときの区切り文字としては、カンマまたはスペースの両方が使えます。

アフィン変換 (移動、スケール、回転など) について

3D オブジェクトのアフィン変換は、ModelVisual3D.Transform プロパティで指定します。

コードでの記述について

XAML の代わりに C# などの .NET のコードで記述することもできます。
同一の処理が繰り返される場合には、コードで生成したほうが簡単に表現できることもあります。

 

次回: WPF で 3D オブジェクトを回転させる

作成したサンプル
Wpf3DSample (GitHub): コードで描画した例もあります。

バージョン情報
.NET Framework 4.5

参照
3-D グラフィックスの概要
3-D 変換の概要

マウスのドラッグ イベントを Rx で実装する

WPF などの GUI アプリケーションでマウスのドラッグ操作を扱うには、
例えばマウスのドラッグ中の各点の情報も通知するなどの要件に合わせて、
基本的には自分で実装することになると思います。

従来であれば、MouseDown などの各イベントごとにイベントハンドラーを登録して実装していましたが、
リアクティブ プログラミングにすることで、より抽象度の高い形式で扱えるようになります。
今回は、WPF のマウスのドラッグ イベントを Reactive Extensions (Rx) で実装してみます。

WPF アプリケーション プロジェクトを作成して、NuGet で System.Reactive をインストールします。
そして次のクラスを作成します。

イベントと IObservable は、本質的には等価です。
まず Observable.FromEventPattern メソッドを使って、
MouseDown などの既存のイベントを IObservable<MouseEventArgs> に変換します。

ドラッグの実装方法については、
MouseDown が発生してから MouseUp または MouseLeave が発生するまでの間、MouseMove を通知させます。
これが MouseDown の発生のたびに開始するため、
MouseDrag の型は IObservable<IObservable<Vector>> のような入れ子になります。
ここでは、MouseDown イベントが発生した位置からの変位を通知することにしています。
(要件によって実装方法は異なるでしょう。)

次に、上で実装した MouseDrag を利用するには、次のコードのようになります (主にコンストラクターの部分)。
完全なソースコードは MouseRx2Wpf (GitHub) にあります。

ウィンドウには、変位とそれを表す 8 方向の矢印を表示しています。

MouseRx2Wpf

 

この方法はマウス以外にも応用できます。
Leap Image Pinch は、Leap Motion Controller を使って空中で 2 本の指でつまんで画像をドラッグする例です。

作成したサンプル
MouseRx2Wpf (GitHub)

バージョン情報
.NET Framework 4.5

参照
System.Reactive
Reactive Extensionsの概要と利用方法
Leap Image Pinch