[番外編 #3] 軸回転をしてみよう

<!--
 <font color="c00000"></font> 赤
-->
## はじめに
皆様お疲れ様です。
今回の **「番外編」** では<font color="c00000"> **「軸回転」** </font>についての記事を作成して参ります。

:rolling_eyes: 運動会の種目で見たことあるようなやつですか？
:upside_down: 多分複数人で棒を運ぶあれだと思います。カラーコーンとかを軸とした時に選手と棒が回転するあの感じでイメージは似てますね（笑）
![a4d62b0a72a2a2199162a0c7e312011e_t.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3806875/27621b27-44a5-2636-6578-57267d4ab009.jpeg)


## 軸回転の仕組み
軸回転のイメージをみていきましょう（下記の図をご覧ください）
![bangai03_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3806875/4efb3d4c-24b8-cb45-9563-adea60818715.png)
![bangai03_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3806875/fde3419b-4b1f-9236-551a-aedaa234113f.png)
![bangai03_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3806875/f383a069-02a1-45c3-6f08-3e12eb4c3dfe.png)
ざっとこんな感じです。では次にこの図を基にコードを作成してみたいと思います。
## 作成したコード
今回もDxLib C++ を使用しております。下記がコードです。
```c++
// ヘッダーファイル
#include "DxLib.h"
#include "iostream"

// 定数値.
#define PAULS 15			// ポールの数.

// float変数.
float VectorX;		// ベクター変数X.
float VectorY;		// ベクター変数Y

float rad;			// ラジアン.
float rRadian;		// 回転の半径.
float theta;		// θ（シータ：sinθ/角度を表す記号としてよく使われるもの）.
float degree;		// 度合い.
float π;			// 3.14

// int変数.
int Mouse;			// マウスの入力状態を取得する.
int direct;			// ボールの進行方向.
int cx;				// 回転軸座標(X).
int cy;				// 回転軸座標(Y).
int color;			// ボールの枠の色.
int angle;			// 角度.

int PlayX;			// ボールのX軸.
int PlayY;			// ボールのY軸.
int PauleX[PAULS];	// ポール座標X.
int PauleY[PAULS];	// ポール座標Y.

int TxCr = GetColor(255, 255, 255);			// テキスト用（白色）.
int TxCr2 = GetColor(0, 255, 255);			// デバッグテキスト用（青緑色）.
int TxCr3 = GetColor(255, 0, 0);			// デバッグテキスト用（赤色）.

int B_multip;			// ゴルフボールの移動乗算.
int V_multip;			// 反射用ベクター乗算.

// bool変数.
bool onTarnFlg;								// 回転しているか？.

// 初期化処理.
void init()
{
	degree = 0;

	// ポールの座標を指定.
	PauleX[0] = 100,  PauleY[0] = 100;
	PauleX[1] = 100,  PauleY[1] = 250;
	PauleX[2] = 100,  PauleY[2] = 400;
	PauleX[3] = 100,  PauleY[3] = 550;
	PauleX[4] = 100,  PauleY[4] = 700;
	
	
	PauleX[5] = 500, PauleY[5] = 100;
	PauleX[6] = 500, PauleY[6] = 250;
	PauleX[7] = 500, PauleY[7] = 400;
	PauleX[8] = 500, PauleY[8] = 550;
	PauleX[9] = 500, PauleY[9] = 700;
	

	PauleX[10] = 900, PauleY[10] = 100;
	PauleX[11] = 900, PauleY[11] = 250;
	PauleX[12] = 900, PauleY[12] = 400;
	PauleX[13] = 900, PauleY[13] = 550;
	PauleX[14] = 900, PauleY[14] = 700;

	// 回転の半径.
	rRadian = 50;

	// ボールの初期座標.
	PlayX = 100; 
	PlayY = 600;

	// ボールの初期ベクトル.
	VectorX = sin(0);
	VectorY = cos(0);

	π = 3.14;
	angle = 360;
	B_multip = 10;
	V_multip = -1;
}
```

```c++
/*********************************************************************************
	 
	「概要仕様」
	・ボールに判定用の枠を用意し、ポールと判定をとること。
	・判定が取れた時にマウスの左クリックでポールを掴むようにすること。
	・円軌道に沿って等速回転することに挑戦（三角関数を使った座標回転と角速度の計算）.
	・左クリックを離したらボールがポールから離れるようにすること。
	・画面外に出ないように壁反射も実装すること（ボールが画面から出ないように）。
	・自機となるボールに枠を付けてポールとの判定を取ること。

	『二次元平面上の回転計算』

	・回転の中心点を原点とします。
	・回転させたい点の座標を (x, y) とします。
	・回転角度を θ とします。
	・点 (x, y) を回転させるには、以下の式を使用します。

	新しい点の x 座標 = x * cos(θ) - y * sin(θ)
	新しい点の y 座標 = x * sin(θ) + y * cos(θ)
	これにより、元の座標 (x, y) が回転角度 θ だけ回転した新しい座標が得られます。


	[ポイント①]
	ポールを中心軸として動かしたいオブジェクトを回転させるベクターは、
	オブジェクトの位置からポールを指す相対ベクターに対して、『直角の方向』を向きます。
			
	[ポイント②]
	ベクターのｘｙ成分を入替えた後、片方の成分に－1をかけて符号を反転させると、ベクターの向きが直角に回転します。
	またその際、ｘ成分を符号反転させると－90°回転し、ｙ成分を符号反転させると＋90°回転します。

	[ポイント③]
	動かしたいオブジェクトの位置からポールを指す相対ベクターを、そのままの長さで
	ピボットベクターに使うと、ネコとポールの距離によって、ピボット回転の速度が変わってしまいます。

	回転速度を一定に保つには、相対ベクターを正規化する必要があります。
	※正規化ベクターは、長さが「１」の「単位ベクター」になります。
									

	[ポイント④]
	①三平方の定理で相対ベクター：Cの距離を測る
	② 相対ベクターのXY成分をCの距離で割りC:XとC:Yの比率を出す
	③C:XとC:Yの比率が正規化ベクターのXY成分になる
	ベクターを正規化できる「Normalize」が使える.
									
*********************************************************************************/

// ヘッダファイルを読み込む.
#include"Pivot.h"
#include"math.h"

// プログラムは WinMain から始まります
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	ChangeWindowMode(true);			// trueにするとウインドウモードで起動.
	if (DxLib_Init() == -1)			// ＤＸライブラリ初期化処理.
	{
		return -1;					// エラーが起きたら直ちに終了.
	}
	SetDrawScreen(DX_SCREEN_BACK);	// 描画先を裏画面に設定.
	SetGraphMode(1920, 1080, 32);	// 実行画面のスクリーンサイズ(画面サイズX, 画面サイズY ,カラービット数 (`16 or 32` 基本的には32bitを使う) ).
	SetBackgroundColor(105, 105, 105);
	init();

/*************************************************************************************
		メモ：色々とメインループでは必ず実行しなくてはならないものが増えてきたので、
		ここで必要なものをまとめたメインループの骨格を完成させましょう。

		必要な関数は、
		ProcessMessage(): Windows のウインドウメッセージ（終了など）を処理してくれる関数
		ScreeFlip():バックバッファを切り替えてフロントにする関数
		ClearDrawScreen():バックバッファをクリアする関数
		です。
	*************************************************************************************/
	while (1)
	{	
		if (ProcessMessage() != 0) {			// メッセージ処理.
			break;
		}
		
		if (CheckHitKey(KEY_INPUT_ESCAPE) != 0)	// ESCAPEキーが押されているか調べる.
			break;								// 押されたらメインループを抜ける.

		ClearDrawScreen();						// 画面を消す(画像が重複されるため).
		Mouse = GetMouseInput();				// マウスの入力状態取得.

		// ポールの描画.
		for (int i = 0; i < PAULS; i++)
		{
			DrawCircle(PauleX[i],PauleY[i], 7, GetColor(255,255, 0), TRUE);
		}

		// ピボット回転処理.
		// マウスの左ボタンが押されていたら回転する.
		if (Mouse && MOUSE_INPUT_LEFT && onTarnFlg)
		{		
			// DrawString(30, 70, "ポールとボールの枠が触れています", TxCr3); // デバッグテキスト用.
			// 『回転軸の計算]』.
			// 回転の方向をdirectを掛けて指定する.
			degree += (10 * direct);				// 回転速度の計算、数値が高い程回転が速くなります.
			rad = degree * π / angle;

			PlayX = cx + rRadian * cos(rad);
			PlayY = cy + rRadian * -sin(rad);
	
			if (direct == 1)
			{
				// 頂点を配置する角度を計算をして自機ボールのベクトルの代入.
				/**********************************************************************************************************************
				atan2f 関数は引数 y と x に対し、原点と座標 ( x , y ) を結ぶ直線の角度を求める関数です（直線と x 軸とのなす角の角度）。 
				原点以外の２つの座標を結ぶ直線の角度を求めたい場合は、一方の座標が原点 ( 0 , 0 ) に移動するように
				両方の座標を並行移動させてから atan2f 関数を実行します。
				***********************************************************************************************************************/
				theta = atan2f(cx - PlayX, cy - PlayY);
				VectorX = -sin(theta);		// ボールの向きベクトル（x成分）.
				VectorY = cos(theta);		// ボールの向きベクトル（y成分）.
			}
			// ポールからボールまでを繋ぐ線.
			DrawLine(cx, cy, PlayX, PlayY, GetColor(255, 255, 255), TRUE);
			
		}
		// 押されていない時はそのまま移動する/壁反射も実装.
		else
		{
			// ボールを移動する処理.
			PlayX = PlayX + VectorX * B_multip;
			PlayY = PlayY + VectorY * B_multip;	

			// 壁反射の実装(X,Y).
			if (PlayX < 0 || PlayX > 1520)
			{
				VectorX = VectorX * V_multip;
			}

			if (PlayY < 0 || PlayY > 850)
			{
				VectorY = VectorY * V_multip;
			}

			// ポールとの当たり判定処理（ポール数15個との判定/ポールの数だけループする）.
			for (int i = 0; i< PAULS; i++)
			{
				// ボール枠とポールの判定（ポールのx座標とy座標が指定した範囲以下か？）.
				// 数値を上手く調整しないと判定が取れません.
				if (((PauleX[i] - PlayX) * (PauleX[i] - PlayX)) + ((PauleY[i] - PlayY) * (PauleY[i] - PlayY)) <= 2500)
				{
					DrawString(30, 70, "ポールとボールの枠が触れています", TxCr3);//デバッグテキスト用.
					color = GetColor(255, 0, 0);		// 指定した範囲に触れてたらポールの色を赤に.
					onTarnFlg = TRUE;					// 回転できる.
					cx = PauleX[i];						// ポールの配列座標に回転軸座標(X)を代入.
					cy = PauleY[i];						// ポールの配列座標に回転軸座標(Y)を代入.

					// 回転方向の指定.
					if (PauleX[i] - PlayX < 0)
					{
						direct = TRUE;		// 進行方向をTRUEに.
					}
					 break;					// ループを抜ける.
				}
				// ゴルフボールとポールが当っていない時.
				else
				{
					// DrawString(30, 50, "ポールとボールの枠が触れていません", TxCr2); // デバッグテキスト用
					color = GetColor(255, 255, 255);									// 触れてなかったら白に.
					onTarnFlg = FALSE;													// 触れてないのであれば回転させない.
				}
			}
			
		}
		ScreenFlip();			// 裏画面のデータを表画面に反映させる関数.
	}
	DxLib_End();				// ＤＸライブラリ使用の終了処理
	return 0;				// ソフトの終了 
}

```


### 動作確認
上記で作成したコードを実行してみます。
※ボールの周りはポールとの**接触判定線**になります。（触れると色が変わる仕様にしてます）

図のようにボールがポールに対して軸回転ができるようになりました！
![bandicam2024-09-0816-56-23-007-ezgif.com-video-to-gif-converter.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3806875/ae30ec16-7a3f-12c3-0268-475e32bc809a.gif)


:::note warn
あくまで一例のやり方です。<font color="c00000">**参考程度**</font>としてください
:::

## ちなみに
今回実装に組み込んでおりませんが、現在ボールの回転方向は左回転です。しかし本来はボールの進行方向に対して、<font color="c00000">**「ポールの位置」**</font> が<font color="c00000">**「左右」**</font>どっちにあるかによって回転方向を切り替えるとより**自然**な動きに見えますね。

これは **「ベクターの外積」** を使用すると、進行方向に対してポールの位置が左右どちらか判定できます。

## おわりに
ベクトルの仕組みは私も完璧ではないところが多々あります。気分転換がてらで楽しんで頂けたら幸いです。

今回は **「軸回転」** の動きについて紹介いたしました。

この[番外編]では今後も**ちょっと変わった**記事を作成して参ります。
最後まで読んで頂きありがとうございました！
> **「サイト」**
>[【Unity】ベクトルを正規化する](https://nekojara.city/unity-normalize-vector)
> **「サイト」**
[Vector3.normalized](https://docs.unity3d.com/ja/2020.3/ScriptReference/Vector3-normalized.html)