進め、自分。

ゲームプログラマがC++のことなどを。

角度クラス 解説

角度クラスが一通り実装できたので解説を。(3年越しだな…)

まずは使い方。

void func( Angle a )
{
  ...
}
~~~
Angle a(0.5f); // 180度
Degree d(180.f); // 180度
Radian r(pi); // 180度

// どれでもOK
func(a);
func(d);
func(r);

// 四則演算
//a += 0.1f; // NG 単位の不明な数値との算術は禁止
a += Angle(0.1f); // OK
a += d; // OK

// 比較
Angle(0.1f) == Angle(0.5f); // false
Angle(0.1f) < Angle(0.5f); // true

a.value(1.f); // 値セット
auto v = a.value(); // 値ゲット

auto a2 = a.toAngle(); // Angle型へ変換
auto d2 = a.toDegree(); // Degree型へ変換
auto r2 = a.toRadian(); // Radian型へ変換

a = Angle(-0.5f).abs(); // 絶対値 =0.5f

// 正規化 [0.0~1.0)の範囲に変換 = [0度~360度)
a = Angle(1.5f).normalize(); // 0.5f
a = Angle(-1.25f).normalize(); // 0.75f

// 正と負それぞれの方向で正規化 (-1.0~1.0) = (-360度~360度)
a = Angle(-1.25f).signedNormalize(); // -0.25f

// 角度差を計算(符号あり最短距離)[-180度~180度)
d = Degree(10.f).distanceFrom( Degree(350.f) ); // -20.f

// 指定した角度を中心に反転させた角度を計算する
d = Degree(10.f).reflect( Degree(30.f) ); // 50.f
d = Degree(350.f).reflect( Degree(10.f) ); // 390.f

// 三角関数
v = a.sin();
v = a.cos();
v = a.tan();

// 定数
d = Degree::Zero(); // 0
d = Degree::One();  // 360
d = Degree::Half(); // 180

ざっとこんな感じで。

続いて実装方法。
まずはコアなクラステンプレート部分。

template <typename T, template <typename> class Policy>
class BasicAngles;

Tは値の型。具体的にはfloatやdoubleです。
PolicyはAngle,Degree,Radianの特性を反映する部分ですね。
実際下記のように定義しています。

/// 正規化角度ポリシー
template <typename T>
struct AnglePolicy
{
	static constexpr T kCycle = static_cast<T>(1.0); ///< 1周の値
};

/// ラジアンポリシー
template <typename T>
struct RadianPolicy
{
	static constexpr T kCycle = Konst<T>::pi2; ///< 1周の値
};

/// デグリーポリシー
template <typename T>
struct DegreePolicy
{
	static constexpr T kCycle = static_cast<T>(360); ///< 1周の値
};

せっかくポリシー分けたんですけど、見ての通り1周の値しかないです。
まあ、3種類の違いってそこしかないですからね。

で、これらから次の定義をしています。

/// 正規化角度クラス
template <typename T>
using BasicAngle = detail::BasicAngles<T, detail::AnglePolicy>;

/// ラジアンクラス
template <typename T>
using BasicRadian = detail::BasicAngles<T, detail::RadianPolicy>;

/// デグリークラス
template <typename T>
using BasicDegree = detail::BasicAngles<T, detail::DegreePolicy>;

double型のRadianを作りたかったら、

BasicRadian<double>

を使います。

ここから更に、次の定義をしています。
自分は普段 double を使うことがほとんどないので、
floatが標準として扱いやすいようにですね。
構成としてはstd::stringとstd::basic_stringの関係を意識してます。

/// 標準の正規化角度クラス
using Angle = BasicAngle<float>;

/// 標準のラジアンクラス
using Radian = BasicRadian<float>;

/// 標準のデグリークラス
using Degree = BasicDegree<float>;

型を柔軟に吸収したい場合は次の関数を使います。

template <typename T>
constexpr BasicAngle<T>  MakeAngle( T value ) noexcept { return BasicAngle<T>(value); }

template <typename T>
constexpr BasicRadian<T>  MakeRadian( T value ) noexcept { return BasicRadian<T>(value); }

template <typename T>
constexpr BasicDegree<T>  MakeDegree( T value ) noexcept { return BasicDegree<T>(value); }

ざっと以上ですね。
実装の中身は下記のリンクからどうぞ。

tofu/Angle.hpp at develop · fsawa/tofu · GitHub
tofu/Angle.cpp at develop · fsawa/tofu · GitHub

角度クラス 実装その2

角度クラス、実装していったら
やっぱりラジアン、デグリー、360度を1.0で表す3種類を
1つのテンプレートで実装することになりました…

ひとまず現在の実装状態は↓から。

tofu/Angle.hpp at develop · fsawa/tofu · GitHub
tofu/Angle.cpp at develop · fsawa/tofu · GitHub

一通り実装できたら個々の実装解説みたいな記事を書こうかなと。

次は三角関数と角度反射計算を実装予定。

角度クラス 実装その1

少しずつですが、角度クラス実装してます。

以前の構想ではラジアン、デグリーと、360度を1.0で表す3種類を扱う予定でしたが、
改めて考えてみると、1.0で表すクラス1つでいいんじゃないかと思ってきました。

もちろん、ラジアンやデグリー値との相互変換は必要だし変換コストもかかりますが、
そんなに負荷は気にならないんじゃないなかーと。
正規化しようとしたら 1.0 で扱ってる方がやりやすかったりもするので。
まあ、もっと実装進めていったら考え変わるかもしれませんが…
※3種類を1つのクラステンプレートで実装する方がやりがいあって楽しいですし

今のところ

  • 四則演算
  • 比較演算子
  • 正規化
  • 最短距離計算

を実装しました。
次はラジアンやデグリーからの変換やらを実装予定。

あと、この角度クラスの定数値を定義しようとして、
constexprを少し調べてました。
クラスをconstexprとして扱うのはなかなかめんどくさいですねー。
いや、めんどくさいっていうか修飾子が多い。
コンストラクタが
constexpr explicit Hoge(...) noexcept {...}
とか修飾子だらけ。
c++11以降をバリバリ使っている方々はnoexceptとかまじめにつけてるもんなんでしょうか?

型情報クラス作成

型情報クラス、最低限必要なところは実装完了。
一応GitHubで見られます。
tofu/TypeInfo.hpp at develop · fsawa/tofu · GitHub
tofu/TypeInfo.cpp at develop · fsawa/tofu · GitHub

あとは参照型の対応と、継承関係の検出をやりたいけどそれはまた後で。
次は、ずっとやりたかった角度クラスの実装予定。

RTTIを使わない型名の取得

ひとまず単体テストのプロジェクトが作成できました。
コンパイラを「Clang with Microsoft CodeGen」にしていて、
Clang用のプロジェクトテンプレートにはライブラリしか無いので
後から実行形式に設定を変えるなど。
VS2017のClangがどういうバージョンなのかまだ調べて無いですが…。

テスト自体は最低限動かせるところまでと、
自作の型情報クラスの動作を軽く確認出来るところまで。

型情報クラスでは型名を文字列で取得出来るように対応しました。
あ、RTTIは使わない方針なので、typeidとかは使わずに
自前でゴリゴリ型名を抽出してます。

抽出方法ですが、下のコードで __PRETTY_FUNCTION__ のマクロの部分、
例えばテンプレート引数が int だった場合は
"TTypeInfo<int>::TTypeInfo() [T = int]"

という文字列に展開されます。

template <typname T>
class TTypeInfo
{
public:
	TTypeInfo()
	{
		__PRETTY_FUNCTION__; // ここ
	}
};

これは Clang の場合で、
msvcだと確か __FUNCTION__ マクロを使う必要があって、
"TTypeInfo<int>::TTypeInfo"
という文字列に展開されたはず。
おいおいmsvcにも対応するつもりなのでまたその時確認します。

それで、上記の文字列から型名部分を抽出して取得できるようにしています。
ソースコードはもう少し整理できたら公開しようと思ってます。
githubにはプッシュしてありますが。

次はもう少しTypeInfo用のテストコードを書く予定。

単体テスト準備

ひとまずライブラリが最低限ビルド出来る段階になったので、
今度は単体テストで動作確認をしたくなりました。

テスティングフレームワークはGoogleTestを使ったことがありますが、
今回はとあるフレームワークを試してみることに。

それはgithubで公開されているので、
自分のリポジトリにSubmoduleとして参照することに。
SourceTreeからポチポチっと設定して簡単に落としてくることができました。

今日はここまで。
次はテスト用プロジェクトを作成して実際にテストを書く予定。

countof

自作ライブラリ、手始めに型情報クラスを実装。
それに伴う最低限必要な機能をあれこれ実装した。
アサートマクロとか、コンパイラ毎の違いを吸収するマクロとか。

で、配列の要素数があっているかチェックする静的アサートのマクロも
実装しておこうと思って、C++11以降での配列要素数取得について調査。
仕事でプログラムかいてた時はC++03までしか触れられなかったので
マクロで対応していたけど、C++11以降なら constexpr でスマートに書けるはず!

まあその情報はすぐに出てきて想像通りだったので
素直に countof 関数を実装したんだけど、
C++17以降は size 関数で同様の機能が提供されるらしい。
生の配列でも、sizeメンバ関数を持つコンテナでもOK。
↓のサイトが参考になった。
Better array 'countof' implementation with C++ 11 (updated)

型情報クラスについてはまた次の機会に書きたいと思う。