はじめに
皆様お疲れ様です。
今回の 「番外編」 では 「軸回転」 についての記事を作成して参ります。
運動会の種目で見たことあるようなやつですか?
多分複数人で棒を運ぶあれだと思います。カラーコーンとかを軸とした時に選手と棒が回転するあの感じでイメージは似てますね(笑)
軸回転の仕組み
軸回転のイメージをみていきましょう(下記の図をご覧ください)
ざっとこんな感じです。では次にこの図を基にコードを作成してみたいと思います。
作成したコード
今回もDxLib 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;
}
/*********************************************************************************
「概要仕様」
・ボールに判定用の枠を用意し、ポールと判定をとること。
・判定が取れた時にマウスの左クリックでポールを掴むようにすること。
・円軌道に沿って等速回転することに挑戦(三角関数を使った座標回転と角速度の計算).
・左クリックを離したらボールがポールから離れるようにすること。
・画面外に出ないように壁反射も実装すること(ボールが画面から出ないように)。
・自機となるボールに枠を付けてポールとの判定を取ること。
『二次元平面上の回転計算』
・回転の中心点を原点とします。
・回転させたい点の座標を (x, y) とします。
・回転角度を θ とします。
・点 (x, y) を回転させるには、以下の式を使用します。
新しい点の x 座標 = x * cos(θ) - y * sin(θ)
新しい点の y 座標 = x * sin(θ) + y * cos(θ)
これにより、元の座標 (x, y) が回転角度 θ だけ回転した新しい座標が得られます。
[ポイント①]
ポールを中心軸として動かしたいオブジェクトを回転させるベクターは、
オブジェクトの位置からポールを指す相対ベクターに対して、『直角の方向』を向きます。
[ポイント②]
ベクターのxy成分を入替えた後、片方の成分に-1をかけて符号を反転させると、ベクターの向きが直角に回転します。
またその際、x成分を符号反転させると-90°回転し、y成分を符号反転させると+90°回転します。
[ポイント③]
動かしたいオブジェクトの位置からポールを指す相対ベクターを、そのままの長さで
ピボットベクターに使うと、ネコとポールの距離によって、ピボット回転の速度が変わってしまいます。
回転速度を一定に保つには、相対ベクターを正規化する必要があります。
※正規化ベクターは、長さが「1」の「単位ベクター」になります。
[ポイント④]
①三平方の定理で相対ベクター: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) // DXライブラリ初期化処理.
{
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 軸とのなす角の角度)。
原点以外の2つの座標を結ぶ直線の角度を求めたい場合は、一方の座標が原点 ( 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(); // DXライブラリ使用の終了処理
return 0; // ソフトの終了
}
動作確認
上記で作成したコードを実行してみます。
※ボールの周りはポールとの接触判定線になります。(触れると色が変わる仕様にしてます)
図のようにボールがポールに対して軸回転ができるようになりました!
あくまで一例のやり方です。参考程度としてください
ちなみに
今回実装に組み込んでおりませんが、現在ボールの回転方向は左回転です。しかし本来はボールの進行方向に対して、「ポールの位置」 が「左右」どっちにあるかによって回転方向を切り替えるとより自然な動きに見えますね。
これは 「ベクターの外積」 を使用すると、進行方向に対してポールの位置が左右どちらか判定できます。
おわりに
ベクトルの仕組みは私も完璧ではないところが多々あります。気分転換がてらで楽しんで頂けたら幸いです。
今回は 「軸回転」 の動きについて紹介いたしました。
この[番外編]では今後もちょっと変わった記事を作成して参ります。
最後まで読んで頂きありがとうございました!
「サイト」
【Unity】ベクトルを正規化する
「サイト」
Vector3.normalized