DOSEIの日記

技術メモ+日常ログ

ワールド座標変換

今, 考えるワールド座標系を x軸右向き, y軸上向きの数学で良く使う座標系であるとする.
GDI+ で描画の際に与える座標系や幅の単位はこの座標系で指定する。

ウィンドウのクライアント領域に対して, 左上を原点(固定です), 右下に向かって正である(これも固定)座標系をページ座標系という.

この間の座標変換をワールド変換といって, Graphics.Transform が保持している。

いま考えているワールド座標とページ座標は互いに上下逆さまであるが、平行移動の位置関係は任意である。ここでは、ワールド座標に視点 (wx, wy) (ワールド座標) を指定して、それがクライアント領域の丁度中央 (cx, cy) (ページ座標) にくるような変換を書く。

手順としては,

  • (wx, wy) が原点になるように平行移動
  • 上下をひっくりかえす
  • 原点を (cx, cy) に平行移動

となるので,
変換行列は

 [   1   0 0 ][ 1  0 0 ][  1  0 0 ]
 [   0   1 0 ][ 0 -1 0 ][  0  1 0 ]
 [ -wx -wy 1 ][ 0  0 1 ][ cx cy 1 ]

となる( GDI+ では座標を横ベクトルで表現することに注意)。
これを Graphics.Transform に代入すればいいが、Matrix クラスには Translate() や Scale() などというメソッドがあるので、それを使うほうが読みやすい。また、 Graphics.Transform で保持している Matrix を操作するメソッド (**Transform() ) が Graphics には用意されているので

  g.ResetTransform(); // Graphics::Transform の初期値は単位行列なのでなくてもいい
  g.TranslateTransform(-wx, -wy, MatrixOrder.Append);
  g.ScaleTransform(1.0f, -1.0f, MatrixOrder.Append);
  g.TranslateTransform(cx, cy, MatrixOrder.Append);

とすればいい。ただし、 g.Transform.Scale(a, b); としてはいけない。 Graphics.Transform プロパティは常にオブジェクトのコピーを生成するので、結局この文では Graphics の設定が変化しないことになる。
ちなみに, MatrixOrder.Append とは行列を右から掛けることを指示する。指定しないと左から掛かる。*1

ページ座標で表されたものを実際に画面のピクセルに対応させる(バイス座標系)変換がページ変換である。デバイス座標系の原点と方向は一致しているので、要するにページ座標系に単位を与えて、それが何ピクセルに対応するのかを決定する変換である。
Graphics::PageUnit がその単位を表す。デフォルトは GraphicsUnit.Pixel で((Display かも。まぁいいや))、ページ座標の 1 が 1pixel に対応する。 GraphicsUnit.Inch や GraphicsUnit.Millimeter にすると、 Windows の画面のプロパティで設定できる dpi にしたがって算出される。通常 dpi=96 に設定されているだろうから、 GraphicsUnit.Inch の場合, ページ座標の 1 は 96 ピクセルになる。

じゃあ、画面の拡大縮小を実装するにはどうしよう? Graphics::PageScale というものがあるが、これはあまり使えない。単に上記の計算に定数倍を施すだけで、見た目としては、左上を基準に拡大縮小する。でも、やりたいのはワールド座標空間を拡大して表示したいのだ。そうなると、ワールド座標をスケーリングしてページ座標に変換すればいいだろう。*2つまり、 倍率を s として

  g.ResetTransform(); // Graphics::Transform の初期値は単位行列なのでなくてもいい
  g.TranslateTransform(-wx, -wy, MatrixOrder.Append);
  g.ScaleTransform(s, -s, MatrixOrder.Append);
  g.TranslateTransform(cx, cy, MatrixOrder.Append);

とすればいい。

*1:なんでこれがデフォルトなのかよくわかりません。誰か教えて。

*2:ページ座標でインチやらミリやらの物理単位との対応を考慮するという思想からはずれる気がするが