C++で2次元アフィン変換クラスを作る
2次元座標においてある点を別の点に移す変換のなかで、下の基本変換の合成変換として表現できるものをアフィン変換と呼びます。
例えば、(1,2)だけ平行移動させて、原点を中心に30°回転させる変換は、と表すことができます。
平行移動(x: ,y: ) |
拡大縮小 |
回転 |
スキュー変換 |
利用例
AffineTransform2D Tr; Tr.translate(1,2); Tr.rotate(30); Tr.scale(2,3); Tr.print(); Tr.setToRotate(90); Tr.print();
AffineTransform2Dクラス
3次元の行列クラスであるMatrix3Dクラスを利用してアフィン変換をあらわすには、Matrix3Dを継承させる方法と、メンバとして持たせる方法があります。今回はMatrix3Dクラスの演算を利用して簡単に書きたいのでメンバとして持たせることにします。
class AffineTransform2D{ public: //... void setToIdentity(); //恒等変換に設定 void setToRotation(float theta); //回転変換に設定 void setToRotation(float theta, float x, float y); //平行移動→回転変換に設定 void setToScale(float sx, float sy); //スケーリング変換に設定 void setToSkew(float skx, float sky); //スキュー変換に設定 void setToTranslation(float tx, float ty); //平行移動変換に設定 void setToXReflection(); //x軸に関する鏡映変換に設定 void setToYReflection(); //y軸に関する鏡映変換に設定 void scale(float sx, float sy); //スケーリング変換を行う void skew(float skx, float sky); //スキュー変換を行う void translate(float tx, float ty); //平行移動変換を行う void rotate(float theta); //回転変換を行う void reflectX(); //x軸に関する鏡映変換を行う void reflectY(); //y軸に関する鏡映変換を行う void copyTo(AffineTransform2D& transform_dest); //アフィン変換を複製する void setTransform(AffineTransform2D& additionalTrans); //アフィン変換を合成する protected: Matrix3D m_mat;//行列表現 };
基本変換の定義
AffineTransform2Dクラス内で基本行列の定義を与えておくと、メンバ関数の中ではこれらを利用して簡単にコードを書くことができます。
class AffineTransform2D{ //略 protected: #define Trans(tx,ty) Matrix3D( 1, 0, tx, \ 0, 1, ty, \ 0, 0, 1 ) #define Scale(sx,sy) Matrix3D( sx, 0, 0, \ 0, sy, 0, \ 0, 0, 1 ) #define Skew(skx,sky) Matrix3D( 1, skx, 0, \ sky, 1, 0, \ 0, 0, 1 ) #define Rot(theta) Matrix3D( cos(theta), -sin(theta), 0, \ sin(theta), cos(theta), 0, \ 0, 0, 1 ) };
基本変換の実装
例えば平行移動変換はこのように書くことができます。
//平行移動変換に設定 void AffineTransform2D::setToTranslation(float tx, float ty){ m_mat = Trans(tx,ty); }
現在のアフィン変換にさらに平行移動を行うには次のように左から行列をかけるとよいです。
//平行移動変換を行う void AffineTransform2D::translate(float tx, float ty){ m_mat = Trans(tx,ty) * m_mat; }
回転変換では引数を「°」で指定できると便利なので、ラジアンと「°」の変換関数を定義しておきます。
#ifndef PI #define PI 3.141592653589793 #endif #define RAD(theta) float(theta/180*PI) #define DEG(phi) float(phi/PI*180)
角度変換用マクロを使うと、回転変換に関するメンバ関数の実装は次のようになります。
//回転変換に設定 void AffineTransform2D::setToRotation(float theta){ float phi = RAD(theta); m_mat = Rot(phi); } //平行移動→回転変換に設定 void AffineTransform2D::setToRotation(float theta, float x, float y){ float phi = RAD(theta); m_mat = RotXY(phi,x,y); } //回転変換を行う void AffineTransform2D::rotate(float theta){ float phi = RAD(theta); m_mat = Rot(phi) * m_mat; }
アフィン変換を複製したり、アフィン変換同士を連結するメンバ関数も次のように行列の演算を使うと簡単に書くことができます。
//アフィン変換を複製する void AffineTransform2D::copyTo(AffineTransform2D& transform_dest){ transform_dest.getMatrix() = m_mat; } //アフィン変換を合成する void AffineTransform2D::setTransform(AffineTransform2D& additionalTrans){ m_mat = additionalTrans.getMatrix() * m_mat; }
最終的に作成したものです。
AffineTransform.h
#ifndef __AFFINE_TRANSFORM_2D_H #define __AFFINE_TRANSFORM_2D_H #include "Matrix3D.h" class AffineTransform2D{ public: AffineTransform2D(); Matrix3D& getMatrix(); //行列表現を取得する float* operator[](int i); //行列要素を取得する void setToIdentity(); //恒等変換に設定 void setToRotation(float theta); //回転変換に設定 void setToRotation(float theta, float x, float y); //平行移動→回転変換に設定 void setToScale(float sx, float sy); //スケーリング変換に設定 void setToSkew(float skx, float sky); //スキュー変換に設定 void setToTranslation(float tx, float ty); //平行移動変換に設定 void setToXReflection(); //x軸に関する鏡映変換に設定 void setToYReflection(); //y軸に関する鏡映変換に設定 void scale(float sx, float sy); //スケーリング変換を行う void skew(float skx, float sky); //スキュー変換を行う void translate(float tx, float ty); //平行移動変換を行う void rotate(float theta); //回転変換を行う void reflectX(); //x軸に関する鏡映変換を行う void reflectY(); //y軸に関する鏡映変換を行う void copyTo(AffineTransform2D& transform_dest); //アフィン変換を複製する void setTransform(AffineTransform2D& additionalTrans); //アフィン変換を合成する void print(); //行列要素を出力する protected: Matrix3D m_mat; #ifndef PI #define PI 3.141592653589793 #endif #define RAD(theta) float(theta/180*PI) #define DEG(phi) float(phi/PI*180) #define Id Matrix3D( 1, 0, 0, \ 0, 1, 0, \ 0, 0, 1 ) #define Trans(tx,ty) Matrix3D( 1, 0, tx, \ 0, 1, ty, \ 0, 0, 1 ) #define Scale(sx,sy) Matrix3D( sx, 0, 0, \ 0, sy, 0, \ 0, 0, 1 ) #define Skew(skx,sky) Matrix3D( 1, skx, 0, \ sky, 1, 0, \ 0, 0, 1 ) #define Rot(theta) Matrix3D( cos(theta), -sin(theta), 0, \ sin(theta), cos(theta), 0, \ 0, 0, 1 ) #define RotXY(theta,x,y) Matrix3D( cos(theta), -sin(theta), x, \ sin(theta), cos(theta), y, \ 0, 0, 1 ) #define RefX Matrix3D( 1, 0, 0, \ 0, -1, 0, \ 0, 0, 1 ) #define RefY Matrix3D( -1, 0, 0, \ 0, 1, 0, \ 0, 0, 1 ) }; #endif
AffineTransform2D.cpp
#include "AffineTransform2D.h" #include <cmath> //コンストラクタ AffineTransform2D::AffineTransform2D(){ setToIdentity(); } //行列表現の取得 Matrix3D& AffineTransform2D::getMatrix(){ return m_mat; } //添え字演算子 float* AffineTransform2D::operator[](int i){ return m_mat[i]; } //基本変換に設定 void AffineTransform2D::setToIdentity(){ m_mat = Id; } void AffineTransform2D::setToRotation(float theta){ float phi = RAD(theta); m_mat = Rot(phi); } void AffineTransform2D::setToRotation(float theta, float x, float y){ float phi = RAD(theta); m_mat = RotXY(phi,x,y); } void AffineTransform2D::setToScale(float sx, float sy){ m_mat = Scale(sx,sy); } void AffineTransform2D::setToSkew(float skx, float sky){ m_mat = Skew(skx, sky); } void AffineTransform2D::setToTranslation(float tx, float ty){ m_mat = Trans(tx,ty); } void AffineTransform2D::setToXReflection(){ m_mat = RefX; } void AffineTransform2D::setToYReflection(){ m_mat = RefX; } //基本変換 void AffineTransform2D::scale(float sx, float sy){ m_mat = Scale(sx,sy) * m_mat; } void AffineTransform2D::skew(float skx, float sky){ m_mat = Skew(skx,sky) * m_mat; } void AffineTransform2D::translate(float tx, float ty){ m_mat = Trans(tx,ty) * m_mat; } void AffineTransform2D::rotate(float theta){ float phi = RAD(theta); m_mat = Rot(phi) * m_mat; } void AffineTransform2D::reflectX(){ m_mat = RefX * m_mat; } void AffineTransform2D::reflectY(){ m_mat = RefY * m_mat; } //複製 void AffineTransform2D::copyTo(AffineTransform2D& transform_dest){ transform_dest.getMatrix() = m_mat; } //合成 void AffineTransform2D::setTransform(AffineTransform2D& additionalTrans){ m_mat = additionalTrans.getMatrix() * m_mat; } //出力 #include <iostream> void AffineTransform2D::print(){ std::cout << m_mat << std::endl; }