Direct2D での変換の適用
Direct2D を使用した描画では、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);
}
このコードは、時計の面の中心から始まり、ポイント endPoint で終わる垂直線を描画します。 線は、回転変換を適用して楕円の中心を中心に回転します。 回転の中心点は、時計の面を形成する楕円の中心です。
次のコードは、時計全体の顔がどのように描画されるかを示しています。
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 クラスは、行列乗算に operator*() を提供します。 行列を乗算する順序は重要です。 変換 (M × N) を設定すると、"Apply M first, followed by 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 であるために発生します。