Direct2D を使用した Drawing では、ID2D1RenderTarget::FillEllipse メソッドが x 軸と y 軸に揃えた楕円を描画しているのを確認しました。 しかし、角度に傾いた楕円を描画するとします。
変換を使用すると、次の方法で図形を変更できます。
- 点を中心に回転します。
- 拡大縮小。
- 平行移動 (X または Y 方向の変位)。
- 傾斜 ( せん断とも呼ばれます)。
変換は、一連の点を新しい点のセットにマップする数学的演算です。 たとえば、次の図は、点 P3 を中心に回転した三角形を示しています。 回転が適用されると、ポイント P1 が P1 にマップされ、ポイント P2 が P2' にマップされ、ポイント P3 がそれ自体にマップされます。
ポイントの周りの回転を示す図を
変換は、マトリックスを使用して実装されます。 ただし、使用するために行列の数学を理解する必要はありません。 数学の詳細については、「 付録: 行列変換」を参照してください。
Direct2D で変換を適用するには、 ID2D1RenderTarget::SetTransform メソッドを呼び出します。 このメソッドは、変換を定義する D2D1_MATRIX_3X2_F 構造体を受け取ります。 この構造体は、 D2D1::Matrix3x2F クラスでメソッドを呼び出すことによって初期化できます。 このクラスには、変換の種類ごとに行列を返す静的メソッドが含まれています。
たとえば、次のコードは、点 (100、100) の周りに 20 度回転を適用します。
pRenderTarget->SetTransform(
D2D1::Matrix3x2F::Rotation(20, D2D1::Point2F(100,100)));
変換は、 SetTransform をもう一度呼び出すまで、それ以降のすべての描画操作に適用されます。 現在の変換を削除するには、ID マトリックスを使用して SetTransform を呼び出します。 ID 行列を作成するには、 Matrix3x2F::Identity 関数を 呼び出します。
pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
時計針の描画
変換を活用して、Circleプログラムをアナログ時計に変換しましょう。 これを行うには、手の線を追加します。
線の座標を計算する代わりに、角度を計算し、回転変換を適用できます。 次のコードは、1 つのクロック ハンドを描画する関数を示しています。 fAngle パラメーターは、手の角度を度単位で指定します。
void Scene::DrawClockHand(float fHandLength, float fAngle, float fStrokeWidth)
{
m_pRenderTarget->SetTransform(
D2D1::Matrix3x2F::Rotation(fAngle, m_ellipse.point)
);
// endPoint defines one end of the hand.
D2D_POINT_2F endPoint = D2D1::Point2F(
m_ellipse.point.x,
m_ellipse.point.y - (m_ellipse.radiusY * fHandLength)
);
// Draw a line from the center of the ellipse to endPoint.
m_pRenderTarget->DrawLine(
m_ellipse.point, endPoint, m_pStroke, fStrokeWidth);
}
このコードは、時計の面の中心から始まり、 終点の終点で終わる垂直線を描画します。 直線は、回転変換を適用して楕円の中心を中心に回転します。 回転の中心点は、時計面を形成する楕円の中心です。
次のコードは、クロック面全体がどのように描画されるかを示しています。
void Scene::RenderScene()
{
m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::SkyBlue));
m_pRenderTarget->FillEllipse(m_ellipse, m_pFill);
m_pRenderTarget->DrawEllipse(m_ellipse, m_pStroke);
// Draw hands
SYSTEMTIME time;
GetLocalTime(&time);
// 60 minutes = 30 degrees, 1 minute = 0.5 degree
const float fHourAngle = (360.0f / 12) * (time.wHour) + (time.wMinute * 0.5f);
const float fMinuteAngle =(360.0f / 60) * (time.wMinute);
DrawClockHand(0.6f, fHourAngle, 6);
DrawClockHand(0.85f, fMinuteAngle, 4);
// Restore the identity transformation.
m_pRenderTarget->SetTransform( D2D1::Matrix3x2F::Identity() );
}
Direct2D Clock サンプルから完全な Visual Studio プロジェクトをダウンロードできます。 (楽しみのためだけに、ダウンロード バージョンでは、時計の面に放射状グラデーションが追加されます)。
変換の組み合わせ
4 つの基本的な変換は、2 つ以上の行列を乗算することによって結合できます。 たとえば、次のコードは回転と移動を組み合わせたものです。
const D2D1::Matrix3x2F rot = D2D1::Matrix3x2F::Rotation(20);
const D2D1::Matrix3x2F trans = D2D1::Matrix3x2F::Translation(40, 10);
pRenderTarget->SetTransform(rot * trans);
Matrix3x2F クラスは、行列乗算に演算子 *() を提供します。 行列を乗算する順序は重要です。 変換 (M × N) の設定は、「最初に M を適用し、その後に N を適用すること」を意味します。たとえば、回転の後に平行移動が続く場合を示します。
この変換のコードを次に示します。
const D2D1::Matrix3x2F rot = D2D1::Matrix3x2F::Rotation(45, center);
const D2D1::Matrix3x2F trans = D2D1::Matrix3x2F::Translation(x, 0);
pRenderTarget->SetTransform(rot * trans);
次に、その変換を逆順の変換、平行移動、回転順で比較します。
回転は、元の四角形の中心を中心に実行されます。 この変換のコードを次に示します。
D2D1::Matrix3x2F rot = D2D1::Matrix3x2F::Rotation(45, center);
D2D1::Matrix3x2F trans = D2D1::Matrix3x2F::Translation(x, 0);
pRenderTarget->SetTransform(trans * rot);
ご覧のように、行列は同じですが、操作の順序は変更されています。 これは、行列乗算が可換ではなく、M × N ≠ N × M であるために発生します。