DXライブラリでゲームの基礎を作ろう4 モデル、カメラ無限回転編

#最近比べた与太話

```cpp
#define NUMS 50000

#include"DXLib.h"

class testclass {
	int a = 0;
	float b = 0.f;
	double c = 0.0;
public:
	testclass() {
		a = 1;
	}
	testclass(const testclass& tgt) {
		a = tgt.a;
	}
	~testclass() {
		a = 0;
	}
};

int bbi;
void func_1(int a) {
	bbi = a;
	return;
}
void func_2(int* a) {
	bbi = *a;
	return;
}
void func_3(int& a) {
	bbi = a;
	return;
}

testclass bb;
void func_1(testclass a) {
	bb = a;
	return;
}
void func_2(testclass* a) {
	bb = *a;
	return;
}
void func_3(testclass& a) {
	bb = a;
	return;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	//init省略

	//速度比較
	std::list<testclass> test_list;
	test_list.resize(NUMS);
	std::vector<testclass> test_vec;
	test_vec.resize(NUMS);
	std::array<testclass, NUMS> test_array;
	LONGLONG listtime = 0;//リストでイテレーターfor
	{
		const auto waits = GetNowHiPerformanceCount();
		for (auto i = test_list.begin();i != test_list.end();i++) {}//めちゃくちゃ遅い
		listtime = GetNowHiPerformanceCount() - waits;
	}
	LONGLONG listtime2 = 0;//リストで参照範囲for
	{
		const auto waits = GetNowHiPerformanceCount();
		for (auto& i : test_list) {}//まあまあ遅い
		listtime2 = GetNowHiPerformanceCount() - waits;
	}
	LONGLONG listtime4 = 0;//リストで値範囲for
	{
		const auto waits = GetNowHiPerformanceCount();
		for (auto i : test_list) {}//まあまあ遅い
		listtime4 = GetNowHiPerformanceCount() - waits;
	}
	LONGLONG vectortime = 0;//vectorでfor
	{
		const auto waits = GetNowHiPerformanceCount();
		for (size_t i = 0;i < test_vec.size();i++) { auto& t = test_vec[i]; }//すこし遅い
		vectortime = GetNowHiPerformanceCount() - waits;
	}
	LONGLONG vectortime2 = 0;//vectorでイテレーターfor
	{
		const auto waits = GetNowHiPerformanceCount();
		for (auto i = test_vec.begin();i != test_vec.end();i++) {}//めちゃくちゃ遅い
		vectortime2 = GetNowHiPerformanceCount() - waits;
	}
	LONGLONG vectortime3 = 0;//vectorで参照範囲for
	{
		const auto waits = GetNowHiPerformanceCount();
		for (auto& i : test_vec) {}//まあまあ早い
		vectortime3 = GetNowHiPerformanceCount() - waits;
	}
	LONGLONG vectortime5 = 0;//vectorで値範囲for
	{
		const auto waits = GetNowHiPerformanceCount();
		for (auto i : test_vec) {}//まあまあ早い
		vectortime5 = GetNowHiPerformanceCount() - waits;
	}
	test_vec.resize(NUMS);
	LONGLONG vectortime6 = 0;//erase
	{
		const auto waits = GetNowHiPerformanceCount();
		for (int t = 0; t < 1000;t++) {
			test_vec.erase(test_vec.begin() + 4000);//激遅
		}
		vectortime6 = GetNowHiPerformanceCount() - waits;
	}
	test_vec.resize(NUMS);
	LONGLONG vectortime7 = 0;//swapしてpopback
	{
		const auto waits = GetNowHiPerformanceCount();
		for (int t = 0; t < 1000;t++) {
			std::iter_swap(test_vec.begin() + 4000, test_vec.end() - 1);
			test_vec.pop_back();
			//上より早い
		}
		vectortime7 = GetNowHiPerformanceCount() - waits;
	}

	LONGLONG arraytime = 0;//arrayでfor
	{
		const auto waits = GetNowHiPerformanceCount();
		for (size_t i = 0;i < test_array.size();i++) { auto& t = test_array[i]; }//すこし遅い
		arraytime = GetNowHiPerformanceCount() - waits;
	}
	LONGLONG arraytime2 = 0;//arrayでイテレーターfor
	{
		const auto waits = GetNowHiPerformanceCount();
		for (auto i = test_array.begin();i != test_array.end();i++) {}//めちゃくちゃ遅い
		arraytime2 = GetNowHiPerformanceCount() - waits;
	}
	LONGLONG arraytime3 = 0;//arrayで参照範囲for
	{
		const auto waits = GetNowHiPerformanceCount();
		for (auto& i : test_array) {}//まあまあ早い
		arraytime3 = GetNowHiPerformanceCount() - waits;
	}
	LONGLONG arraytime5 = 0;//arrayで値範囲for
	{
		const auto waits = GetNowHiPerformanceCount();
		for (auto i : test_array) {}//まあまあ早い
		arraytime5 = GetNowHiPerformanceCount() - waits;
	}
	LONGLONG functime = 0;//値渡し(クラス)
	{
		testclass a;
		const auto waits = GetNowHiPerformanceCount();
		for (int t = 0; t < NUMS;t++) {
			func_1(a);//上より早い
		}
		functime = GetNowHiPerformanceCount() - waits;
	}
	LONGLONG functime2 = 0;//ポインタ渡し(クラス)
	{
		testclass a;
		const auto waits = GetNowHiPerformanceCount();
		for (int t = 0; t < NUMS;t++) {
			func_2(&a);//上より早い
		}
		functime2 = GetNowHiPerformanceCount() - waits;
	}
	LONGLONG functime3 = 0;//参照渡し(クラス)
	{
		testclass a;
		const auto waits = GetNowHiPerformanceCount();
		for (int t = 0; t < NUMS;t++) {
			func_3(a);//上より早い
		}
		functime3 = GetNowHiPerformanceCount() - waits;
	}
	LONGLONG functime4 = 0;//値渡し(int)
	{
		int a = 0;
		const auto waits = GetNowHiPerformanceCount();
		for (int t = 0; t < NUMS;t++) {
			func_1(a);//上より早い
		}
		functime4 = GetNowHiPerformanceCount() - waits;
	}
	LONGLONG functime5 = 0;//ポインタ渡し(int)
	{
		int a = 0;;
		const auto waits = GetNowHiPerformanceCount();
		for (int t = 0; t < NUMS;t++) {
			func_2(&a);//上より早い
		}
		functime5 = GetNowHiPerformanceCount() - waits;
	}
	LONGLONG functime6 = 0;//参照渡し(int)
	{
		int a = 0;;
		const auto waits = GetNowHiPerformanceCount();
		for (int t = 0; t < NUMS;t++) {
			func_3(a);//上より早い
		}
		functime6 = GetNowHiPerformanceCount() - waits;
	}

	while (ProcessMessage() == 0) {
		const auto waits = GetNowHiPerformanceCount();
		FPS = GetFPS();

		clsDx();
		printfDx("call   :%d\n", GetDrawCallCount());
		printfDx("単位   :マイクロ秒\n");
		printfDx("list\n");
		printfDx("  イテレーターfor    :%d\n", listtime);
		printfDx("  参照範囲for        :%d\n", listtime2);
		printfDx("  値範囲for          :%d\n", listtime4);
		printfDx("vector\n");
		printfDx("  普通のfor          :%d\n", vectortime);
		printfDx("  イテレーターfor    :%d\n", vectortime2);
		printfDx("  参照範囲for        :%d\n", vectortime3);
		printfDx("  値範囲for          :%d\n", vectortime5);
		printfDx("array\n");
		printfDx("  普通のfor          :%d\n", arraytime);
		printfDx("  イテレーターfor    :%d\n", arraytime2);
		printfDx("  参照範囲for        :%d\n", arraytime3);
		printfDx("  値範囲for          :%d\n", arraytime5);
		printfDx("\n");
		printfDx("vector\n");
		printfDx("  途中を消す         :%d\n", vectortime6);
		printfDx("  swapしてpopback    :%d\n", vectortime7);
		printfDx("クラス\n");
		printfDx("  関数の値渡し       :%d\n", functime);
		printfDx("  関数のポインタ渡し :%d\n", functime2);
		printfDx("  関数の参照渡し     :%d\n", functime3);
		printfDx("int\n");
		printfDx("  関数の値渡し       :%d\n", functime4);
		printfDx("  関数のポインタ渡し :%d\n", functime5);
		printfDx("  関数の参照渡し     :%d\n", functime6);
		//画面の反映
		Screen_Flip();
	}
}
```

いろいろ速度を測ってみました。結果は省きます(は？)。デバッグビルドではlistのイテレーターforが激遅でしたが、リリースビルドではvectorのeraceを除いて全部6マイクロ秒以下で終わっちゃいました。
今回の教訓
・可変長コンテナはvector,listいろいろありますが、速度は気にせずとりあえずは用途に応じたものを使っていいでしょう。(mapわからん)
・vectorのeraseは遅いということは心に刻みましょう


#はじめに

今回はモデルの表示とTPS、FPSな動作をするカメラを実装してみましょう。

#4.モデルを描画し、カメラの挙動を作ろう

まずはモデルを描画できるようにしましょう。

##モデルハンドルの組み込み

https://github.com/yabushita0312/TankFlanker

またまたこちらからMV1ModelHandle.hppとGraphHandle.hppを用意しインクルードします。

```cpp
#include "GraphHandle.hpp"
#include "MV1ModelHandle.hpp"
```

今回使うのは MV1ModelHandle.hpp だけですが、そちらの中でGraphHandleを用いているためGraphHandle.hppも用意します。
そしてメインをこのように変更しましょう。自分は**model.mv1**というモデルを用意していますが、任意のモデルを用意してください。

```cpp
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	//コピーしてはいけない場合unique_ptrを使おう
	auto draw = std::make_unique<DXDraw>();
	//リソース
	std::vector<EffekseerEffectHandle> effHndle;
	MV1 model_base;
	//オブジェクト
	std::vector<EffectS> effcs;
	MV1 model_obj;
	//事前用意
	{
		//リソース
		{
			//エフェクトファイルを探す
			effHndle.resize(effHndle.size() + 1);
			effHndle.back() = EffekseerEffectHandle::load("0.efk");
			//モデルリソースを読み込む
			MV1::Load("model.mv1", &model_base, false);
		}
		//オブジェクト
		{
			//エフェクトのセットアップ
			effcs.resize(effHndle.size());
			//モデルオブジェクトを生成
			model_obj = model_base.Duplicate();
		}
	}
	//エフェクトの位置
	VECTOR_ref effect_pos = VECTOR_ref::vget(0.f, 0.f, 0.f);
	//カメラの注視点
	VECTOR_ref camera_target_pos = effect_pos;
	//カメラの位置
	VECTOR_ref camera_pos = effect_pos;
	//演算用の値
	float rad = 0.f;
	float size = 3.f;
	//メインループ
	while (ProcessMessage() == 0) {
		/*演算*/
		{
			//スペースを押しているときエフェクトを設定する
			if (CheckHitKey(KEY_INPUT_SPACE) != 0) {
				effcs[0].set(effect_pos, VGet(0.f, 1.f, 0.f), 0.1f);
			}
			if (CheckHitKey(KEY_INPUT_A) != 0) {
				rad += DX_PI_F / 180 * 6 * 60 / GetFPS();
			}
			if (CheckHitKey(KEY_INPUT_D) != 0) {
				rad -= DX_PI_F / 180 * 6 * 60 / GetFPS();
			}
		}
		//エフェクトの更新
		{
			for (size_t index = 0;index < effHndle.size();++index) {
				effcs[index].put(effHndle[index]);
			}
		}
		/*描画*/
		{
			//3D(主描画)
			{
				camera_target_pos = effect_pos + VECTOR_ref::vget(0.f, 0.8f, 0.f);//注視点
				camera_pos = camera_target_pos + (MATRIX_ref::RotY(rad).zvec() * size);
			}
			draw->SetDraw_Screen(camera_pos, camera_target_pos, VGet(0, 1, 0), DX_PI_F / 4, 0.5f, 20.f);
			Effekseer_Sync3DSetting();
			{
				UpdateEffekseer3D();
				//draw3D
				model_obj.DrawModel();
				DrawEffekseer3D();
			}
			//2D(UI:ユーザーインターフェース)
			{
			}
		}
		ScreenFlip();
		//
		if (CheckHitKey(KEY_INPUT_ESCAPE) != 0) {
			break;
		}
	}
	return 0;// ソフトの終了 
}
```

MV1というモデル用のハンドルを用意し、**MV1LoadModel**と**MV1DuplicateModel**を行う様子です。
今後のためにリソースとオブジェクトを分けてあります。

#マウスエイムを作ろう

TPS、FPS共にPCでプレイするならばマウスで視点移動ができないといけません。そこでマウスの1フレームごとの移動量を取得しましょう。
基本的な手順は

①マウスの現在座標を取得
②マウスを所定の座標に移動
③所定の座標と取得した現在座標との差を算出

をすることで取得できます。(RawInputとかもあります)
この方法は簡単ですがwindowsやマウス側の感度、DPI設定が載ります。
また、DXライブラリのGetMousePoint()は画面外への移動は無効とします為、画面中央にマウスを持ってくる必要があります。

```cpp
void mouse_move(float* x_m, float* y_m, const float fov_per = 1.f) {
	int x_t, y_t;
	GetMousePoint(&x_t, &y_t);//~0.01
	*x_m += float(std::clamp(x_t - deskx / 2, -120, 120)) * fov_per / GetFPS();
	*y_m -= float(std::clamp(y_t - desky / 2, -120, 120)) * fov_per / GetFPS();
	SetMousePoint(deskx / 2, desky / 2);
	SetMouseDispFlag(FALSE);
}
```

こんなものを作ってみました。
deskx,deskyには現在のウィンドウサイズを入れています。
floatで演算しているのはなぜか…というより以前からつけていた**GetFPS()**はなんぞやと言いますと、

##1フレームの時間に応じた補正をするためやな

はい、そうですね。
GetFPSはDXライブラリの関数です。ScreenFlipを呼んだ間隔をもとにした現在のフレームレートを返してくれます。
フレームレートは以前述べた垂直同期が切られていると大幅に変動します。ほぼ何も書いていなければ2000FPSを超えることもありますし、重い描画では60FPSも出せなくなるかもしれません。
そこで、「GetFPSで割ることで1フレームで動く量を調整する」というのが必要になるのです。
そうして調整した値は小数点以下も重要になってきますので、intでなくfloatで小数点以下を保持する必要があったんですね。

ついでに制限を設けるためにclampしてあります。**algorithm**もインクルードしてください。

こちらを組み込んだらこうなります。

```cpp
class DXDraw {
public:
	/*中略*/
	void mouse_move(float* x_m, float* y_m, const float fov_per = 1.f) {
		int x_t, y_t;
		GetMousePoint(&x_t, &y_t);//~0.01
		*x_m += float(std::clamp(x_t - deskx / 2, -120, 120)) * fov_per / GetFPS();
		*y_m -= float(std::clamp(y_t - desky / 2, -120, 120)) * fov_per / GetFPS();
		SetMousePoint(deskx / 2, desky / 2);
		SetMouseDispFlag(FALSE);
	}
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	//コピーしてはいけない場合unique_ptrを使おう
	auto draw = std::make_unique<DXDraw>();
	//リソース
	std::vector<EffekseerEffectHandle> effHndle;
	MV1 model_base;
	//オブジェクト
	std::vector<EffectS> effcs;
	MV1 model_obj;
	//事前用意
	{
		//リソース
		{
			//エフェクトファイルを探す
			effHndle.resize(effHndle.size() + 1);
			effHndle.back() = EffekseerEffectHandle::load("data/effect/0.efk");
			//モデルリソースを読み込む
			MV1::Load("data/model/model.mv1", &model_base, false);
		}
		//オブジェクト
		{
			//エフェクトのセットアップ
			effcs.resize(effHndle.size());
			//モデルオブジェクトを生成
			model_obj = model_base.Duplicate();
		}
	}
	//エフェクトの位置
	VECTOR_ref effect_pos = VECTOR_ref::vget(0.f, 0.f, 0.f);
	//カメラの注視点
	VECTOR_ref camera_target_pos = effect_pos;
	//カメラの位置
	VECTOR_ref camera_pos = effect_pos;
	//演算用の値
	float xrad = 0.f, yrad = 0.f;
	float size = 3.f;
	//メインループ
	while (ProcessMessage() == 0) {
		/*演算*/
		{
			//スペースを押しているときエフェクトを設定する
			if (CheckHitKey(KEY_INPUT_SPACE) != 0) {
				effcs[0].set(effect_pos, VGet(0.f, 1.f, 0.f), 0.1f);
			}
			draw->mouse_move(&yrad, &xrad, 1.f);//マウスの座標からカメラ角度を取得
		}
		//エフェクトの更新
		{
			for (size_t index = 0;index < effHndle.size();++index) {
				effcs[index].put(effHndle[index]);
			}
		}
		/*描画*/
		{
			//3D(主描画)
			{
				camera_target_pos = effect_pos + VECTOR_ref::vget(0.f, 0.8f, 0.f);//注視点
				camera_pos = camera_target_pos + ((MATRIX_ref::RotX(xrad) * MATRIX_ref::RotY(yrad)).zvec() * size);//X軸回転込みのカメラ回転
			}
			draw->SetDraw_Screen(camera_pos, camera_target_pos, VGet(0, 1, 0), DX_PI_F / 4, 0.5f, 20.f);
			Effekseer_Sync3DSetting();
			{
				UpdateEffekseer3D();
				//draw3D
				model_obj.DrawModel();
				DrawEffekseer3D();
			}
			//2D(UI:ユーザーインターフェース)
			{
			}
		}
		ScreenFlip();
		//
		if (CheckHitKey(KEY_INPUT_ESCAPE) != 0) {
			break;
		}
	}
	return 0;// ソフトの終了 
}
``

ADで回転していた部分をxrad、yradとし、マウスの上下に対応しました。マウスが拘束されるので気を付けてくださいね。

#TPS視点

上記の通り、TPSの「特定の視点を中心に周りをまわるようにカメラが移動する」視点が実装できました。詳細を見ましょう。

```cpp
camera_pos = camera_target_pos + ((MATRIX_ref::RotX(xrad) * MATRIX_ref::RotY(yrad)).zvec() * size);//X軸回転込みのカメラ回転
```

この部分、xradを追加していますね。
4元数の掛け算は順序が決まっています。回転演算では基本的に左辺が子、右辺が親と覚えておくといいでしょう。
よってこちらでは
・x軸でxrad回転する
・その状態のものをy軸でyrad回転する
という演算の流れとなっています。

逆向きにしてみると、なんだかおかしな回転になっていることがわかるでしょう。

###…x軸回転無限やんけ

処理後にclampしましょclamp!

##FPS視点

続いてFPSの「特定の視点を中心に首を振るようにカメラが移動する」視点を考えてみましょう。
といってもほぼほぼTPSそのままといえます。

```cpp
camera_pos = effect_pos + VECTOR_ref::vget(0.f, 1.8f, -1.f);//カメラを固定
camera_target_pos = camera_pos + ((MATRIX_ref::RotX(xrad) * MATRIX_ref::RotY(yrad)).zvec() * -size);//注視点
```

はい、注視点とカメラ座標を取り換えただけですね。
回転方向も少し変わるのでsizeを-にしています。



##今回の全体像
今回書いたコードの全体です。
左シフトで視点を切り替えられます。

```cpp
#include "DxLib.h"
#include "EffekseerForDXLib.h"
#include <memory>
#include <array>
#include <vector>
#include <cmath>

#include <algorithm>


#include "DXLib_ref/DXLib_vec.hpp"
#include "DXLib_ref/EffekseerEffectHandle.hpp"
#include "DXLib_ref/GraphHandle.hpp"
#include "DXLib_ref/MV1ModelHandle.hpp"

inline const int32_t deskx{ (int32_t)(640) };
inline const int32_t desky{ (int32_t)(480) };

extern void* enabler;// ダミー変数

//角度からラジアンに
template <class T, typename std::enable_if<std::is_arithmetic<T>::value>::type*& = enabler>
static float deg2rad(T p1) {
	std::is_arithmetic<T>::value;
	return float(p1) * DX_PI_F / 180.f;
}
//ラジアンから角度に
template <class T, typename std::enable_if<std::is_arithmetic<T>::value>::type*& = enabler>
static float rad2deg(T p1) {
	return float(p1) * 180.f / DX_PI_F;
}

//エフェクト
class EffectS {
public:
	bool flug = false;					/*エフェクトの表示フラグ*/
	size_t id = 0;						/*エフェクトのID*/
	Effekseer3DPlayingHandle handle;	/*エフェクトのハンドル*/
	VECTOR_ref pos;							/*エフェクトの場所*/
	VECTOR_ref nor;							/*エフェクトのY軸の向き*/
	float scale = 1.f;					/*エフェクトのスケール*/
	/*エフェクトの場所を指定し開始フラグを立てる*/
	void set(const VECTOR_ref& pos_, const VECTOR_ref& nor_, float scale_ = 1.f) {
		this->flug = true;
		this->pos = pos_;
		this->nor = nor_;
		this->scale = scale_;
	}
	/*エフェクトの開始フラグが立っていればエフェクトを再生*/
	void put(const EffekseerEffectHandle& handle_) {
		if (this->flug) {
			//再生チェック
			if (this->handle.IsPlaying()) {
				this->handle.Stop();
			}
			//再生
			this->handle = handle_.Play3D();
			//場所を指定
			this->handle.SetPos(this->pos);
			//向きを指定
			this->handle.SetRotation(atan2(this->nor.y(), std::hypotf(this->nor.x(), this->nor.z())), atan2(-this->nor.x(), -this->nor.z()), 0);
			//大きさを指定
			this->handle.SetScale(this->scale);
			//開始フラグを切る
			this->flug = false;
		}
	}
};
//いろいろまとめるクラス
class DXDraw {

public:
	//コンストラクタ
	DXDraw() {
		SetOutApplicationLogValidFlag(FALSE);				/*log*/
		SetMainWindowText("game title");					/*タイトル*/
		ChangeWindowMode(TRUE);								/*窓表示*/
		SetUseDirect3DVersion(DX_DIRECT3D_11);				/*directX ver*/
		SetGraphMode(640, 480, 32);							/*解像度*/
		SetUseDirectInputFlag(TRUE);						/*DirectInput使用*/
		SetDirectInputMouseMode(TRUE);						/*DirectInputマウス使用*/
		SetWindowSizeChangeEnableFlag(FALSE, FALSE);		/*ウインドウサイズを手動変更不可、ウインドウサイズに合わせて拡大もしないようにする*/
		SetUsePixelLighting(TRUE);							/*ピクセルライティングの使用*/
		SetFullSceneAntiAliasingMode(4, 2);				    /*アンチエイリアス*/
		SetEnableXAudioFlag(TRUE);							/*XAudioを用いるか*/
		Set3DSoundOneMetre(1.0f);						    /*3Dオーディオの基準距離指定*/
		SetWaitVSyncFlag(TRUE);							    /*垂直同期*/
		DxLib_Init();									    /*ＤＸライブラリ初期化処理*/
		Effekseer_Init(8000);								/*Effekseerの初期化*/
		SetSysCommandOffFlag(TRUE);							/*タスクスイッチを有効にするかどうかを設定する*/
		SetChangeScreenModeGraphicsSystemResetFlag(FALSE);	/*Effekseer用*/
		Effekseer_SetGraphicsDeviceLostCallbackFunctions();	/*Effekseer用*/
		SetAlwaysRunFlag(TRUE);								/*background*/
		SetUseZBuffer3D(TRUE);								/*zbufuse*/
		SetWriteZBuffer3D(TRUE);						    /*zbufwrite*/
		MV1SetLoadModelPhysicsWorldGravity(-9.8f);		    /*重力*/
		SetWindowSize(640, 480);							/*表示するウィンドウサイズ*/
	}
	//デストラクタ
	~DXDraw() {
		Effkseer_End();	/*effkseer終了*/
		DxLib_End();    /*ＤＸライブラリ使用の終了処理*/
	}
	//裏画面のセッティング
	void SetDraw_Screen(const bool& clear = true) {
		SetDrawScreen(DX_SCREEN_BACK);
		if (clear) {
			ClearDrawScreen();
		}
	}
	//裏画面のカメラ情報付きセッティング
	void SetDraw_Screen(const VECTOR_ref& campos, const VECTOR_ref& camvec, const VECTOR_ref& camup, const float& fov, const float& near_, const float& far_) {
		SetDraw_Screen(true);
		SetCameraNearFar(near_, far_);
		SetupCamera_Perspective(fov);
		SetCameraPositionAndTargetAndUpVec(campos.get(), camvec.get(), camup.get());
	}
	//
	void mouse_move(float* x_m, float* y_m, const float fov_per = 1.f) {
		int x_t, y_t;
		GetMousePoint(&x_t, &y_t);//~0.01
		*x_m += float(std::clamp(x_t - deskx / 2, -120, 120)) * fov_per / GetFPS();
		*y_m -= float(std::clamp(y_t - desky / 2, -120, 120)) * fov_per / GetFPS();
		SetMousePoint(deskx / 2, desky / 2);
		SetMouseDispFlag(FALSE);
	}
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	//コピーしてはいけない場合unique_ptrを使おう
	auto draw = std::make_unique<DXDraw>();
	//リソース
	std::vector<EffekseerEffectHandle> effHndle;
	MV1 model_base;
	//オブジェクト
	std::vector<EffectS> effcs;
	MV1 model_obj;
	//事前用意
	{
		//リソース
		{
			//エフェクトファイルを探す
			effHndle.resize(effHndle.size() + 1);
			effHndle.back() = EffekseerEffectHandle::load("data/effect/0.efk");
			//モデルリソースを読み込む
			MV1::Load("data/model/model.mv1", &model_base, false);
		}
		//オブジェクト
		{
			//エフェクトのセットアップ
			effcs.resize(effHndle.size());
			//モデルオブジェクトを生成
			model_obj = model_base.Duplicate();
		}
	}
	//エフェクトの位置
	VECTOR_ref effect_pos = VECTOR_ref::vget(0.f, 0.f, 0.f);
	//カメラの注視点
	VECTOR_ref camera_target_pos = effect_pos;
	//カメラの位置
	VECTOR_ref camera_pos = effect_pos;
	//演算用の値
	SetMousePoint(deskx / 2, desky / 2);
	float xrad = 0.f, yrad = 0.f;
	float size = 3.f;
	//メインループ
	while (ProcessMessage() == 0) {
		/*演算*/
		{
			//スペースを押しているときエフェクトを設定する
			if (CheckHitKey(KEY_INPUT_SPACE) != 0) {
				effcs[0].set(effect_pos, VGet(0.f, 1.f, 0.f), 0.1f);
			}

			draw->mouse_move(&yrad, &xrad, 1.f);
			xrad = std::clamp(xrad, deg2rad(-80), deg2rad(80));
		}
		//エフェクトの更新
		{
			for (size_t index = 0;index < effHndle.size();++index) {
				effcs[index].put(effHndle[index]);
			}
		}
		/*描画*/
		{
			//3D(主描画)
			{
				if (CheckHitKey(KEY_INPUT_LSHIFT) != 0) {
					camera_pos = effect_pos + VECTOR_ref::vget(0.f, 1.8f, -1.f);//カメラを固定
					camera_target_pos = camera_pos + ((MATRIX_ref::RotX(xrad) * MATRIX_ref::RotY(yrad)).zvec() * -size);//注視点
				}
				else {
					camera_target_pos = effect_pos + VECTOR_ref::vget(0.f, 0.8f, 0.f);//注視点を固定
					camera_pos = camera_target_pos + ((MATRIX_ref::RotX(xrad) * MATRIX_ref::RotY(yrad)).zvec() * size);//カメラ座標
				}
			}
			draw->SetDraw_Screen(camera_pos, camera_target_pos, VGet(0, 1, 0), DX_PI_F / 4, 0.5f, 20.f);
			Effekseer_Sync3DSetting();
			{
				UpdateEffekseer3D();
				//draw3D
				model_obj.DrawModel();
				DrawEffekseer3D();
			}
			//2D(UI:ユーザーインターフェース)
			{
			}
		}
		ScreenFlip();
		//
		if (CheckHitKey(KEY_INPUT_ESCAPE) != 0) {
			break;
		}
	}
	return 0;// ソフトの終了 
}
```

こっそり角度とラジアンを変換する関数を入れています。

##まとめ

FPS、TPSカメラが実現できましたね。


##おまけ
後々解説します。

###解説をすっ飛ばしたリスト
・3Dのカメラ設定
	SetCameraPositionAndTargetAndUpVecなどを用いてカメラ座標、注視点、頭の向き、視野角、ニアファーを指定します。
	GraphHandleの解説の際に正式に実装します。

###できたら解説したいリスト
・逆運動学
	モデルの腕制御などで活用しました。難産でしたが意外と仕組みは簡単です。