Siv3Dで物理エンジンのBulletを使う

# OpenSiv3Dで物理エンジンのBulletを使ってみた話
OpenSiv3Dには2D物理演算の機能があります、でも折角なら3Dのゲームが作りたいなと思いましたが、OpenSiv3Dの現行バージョン(0.6.6)には3D空間での物理演算を行う機能はありません。なのでBulletPhysicsを用いて3D物理演算してやろう！！って話です。

## やってみよう！OpenSiv3DとBulletの導入

### 1. Visual Studio 2022のインストール
[公式サイト](https://visualstudio.microsoft.com/ja/vs/)からCommunity 2022をダウンロードします

https://visualstudio.microsoft.com/ja/vs/

指示にそってインストールします。
今回はC++での開発をするので「C++によるデスクトップ開発」のみ選択してインストールします！

### 2. OpenSiv3Dのインストール
[公式サイト](https://siv3d.github.io/ja-jp/)の手順に沿ってインストール

https://siv3d.github.io/ja-jp/

以上！

### 3. Bulletのインストール
##### 1. ソースコードのダウンロードとサンプルの実行
Bullet[公式Github](https://github.com/bulletphysics/bullet3/releases/)から最新のzipもしくはrawをダウンロード

https://github.com/bulletphysics/bullet3/releases/

(展開したフォルダ名を以降 Bullet とします。)

Bullet/build_visual_studio_vr_pybullet_double.bat を実行

Bullet/build3/vs2010 にある 0_Bullet3Solution.sln を実行してプロジェクトを開く

ソリューション操作の再ターゲットと出てくるので

Windows SDK バージョンを最新(__10.0__)
プラットフォームツールセットを __v143へのアップグレード__

にしてすべてにチェックを付けたらOK
(そのままOK)
![ソリューション操作の再ターゲット](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703604/e25a5dcd-bf91-9e0a-6a14-8799cb132693.png)

サンプルが動くか確認します。
ReleaseにしてF5キーを押してデバッグを開始してみます
このような画面が出ていたら成功です。
![スクリーンショット (1474).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703604/3c2e3ced-8e65-d25b-99b5-79ca4cfdd277.png)

画面に出てきた箱を左クリックで持ち上げることができます。
(CtrlかAltと左クリックで画面を動かすことができます。)

他にもいろいろなサンプルがあるので遊んでみてください！

##### 2. OpenSiv3Dで使うためにビルドする

ソリューションエクスプローラーから以下のプロジェクト

+ BulletCollision
+ BulletDynamics
+ BulletSoftBody
+ LinearMath

をビルドします
![ソリューションエクスプローラー](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703604/905b9999-f864-3d25-dcca-453a130b8f26.png)

この時それぞれのプロジェクトのプロパティで

構成: DebugとReleaseそれぞれ
プラットフォーム: x64

構成プロパティ → C/C++ → コード生成 → ランタイムライブラリが

Releaseの時: マルチスレッド(/MT)
Debugの時: マルチスレッドデバッグ(/MTd)

になっていることを確認してください。
![プロジェクトのプロパティ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703604/e1059620-2183-dcd5-24ed-c3b1cf313c80.png)

バッチビルドで先ほどのプロジェクトを
x64でDebug | Releaseそれぞれ(計8つ)にチェックを付けビルドします。
![バッチビルド](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703604/b80372f2-8930-c4de-1db8-c7b1464d57ec.png)
![バッチビルド](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703604/cd3e0959-49c4-2a1b-5017-8536c6cfff69.png)

これでBulletのインストールとビルドは完了です！

### 4. プロジェクトの作成と設定
##### 1. プロジェクトの作成
Visual Studioを開き、新しいプロジェクトの作成
右側のリストからOpenSiv3D_0.6.6のテンプレートを探し、選択して次へ
![プロジェクトの作成](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703604/1b1fc920-47da-b665-4ded-62d98a77c719.png)

名前を決めて作成！

##### 2. プロジェクトの設定
プロジェクトのプロパティで構成をすべての構成になっているのを確認し、

C/C++ → 全般 → 追加のインクルードディレクトリ　に Bullet/src を追加

![追加のインクルードディレクトリ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703604/4a81a7aa-a534-4733-c996-2c3e274f44cc.png)

リンカー → 全般 → 追加のライブラリディレクトリ に Bullet/bin を追加

![追加のライブラリディレクトリ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703604/6fc02371-1af1-2965-1d1a-f879db0e5373.png)

C/C++ → プリプロセッサ -> プリプロセッサの定義 に
```:プリプロセッサの定義
BT_THREADSAFE=1
BT_USE_DOUBLE_PRECISION
```
を追加
![プリプロセッサの定義](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703604/a107e825-2723-ab84-3fcb-1ead611e648c.png)

適用を押して設定を保存し、構成をDebugに変更
リンカー → 入力 → 追加の依存ファイルに
```:追加の依存ファイル | Debug
BulletCollision_vs2010_x64_debug.lib
BulletDynamics_vs2010_x64_debug.lib
BulletSoftBody_vs2010_x64_debug.lib
LinearMath_vs2010_x64_debug.lib
```
を追加
![追加の依存ファイル](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703604/cbfc2807-9869-3a8b-0398-42508aff86a2.png)

適用を押して設定を保存し、構成をReleaseに変更
リンカー → 入力 → 追加の依存ファイルに
```:追加の依存ファイル | Release
BulletCollision_vs2010_x64_release.lib
BulletDynamics_vs2010_x64_release.lib
BulletSoftBody_vs2010_x64_release.lib
LinearMath_vs2010_x64_release.lib
```
を追加

以上でプロジェクトの設定が完了です！

## 実際に動かしてみる

### Hello World! (?)

<details><summary>サンプルコード</summary>

```cpp:main.cpp
#include <Siv3D.hpp> // OpenSiv3D v0.6.6
#include <btBulletDynamicsCommon.h>

void Main() {
	///			 ///
	/// 空間の作成 ///
	///			 ///
	
	// 重力
	btVector3 gravity{ 0, -9.8, 0 };

	// 衝突検知方法の設定
	btCollisionConfiguration* config = new btDefaultCollisionConfiguration{ };
	btCollisionDispatcher* dispatcher = new btCollisionDispatcher{ config };

	// ブロードフェーズ法の設定
	btDbvtBroadphase* broadphase = new btDbvtBroadphase{ };

	// 拘束(剛体間リンク)のソルバ設定
	btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver{ };

	// 空間の作成
	btDynamicsWorld* world = new btDiscreteDynamicsWorld{ dispatcher, broadphase, solver, config };

	// 重力の設定
	world->setGravity(gravity);

	///			 ///
	/// 剛体の作成 ///
	///			 ///

	// 質量
	double mass = 1.0;

	// 中心位置
	btVector3 center{ 0, 0, 0 };

	// 形状の設定 (箱)
	btCollisionShape* shape = new btBoxShape{ btVector3{ 1, 1, 1 } };

	// 初期位置と姿勢
	btMotionState* state = new btDefaultMotionState{ btTransform{ btQuaternion::getIdentity(), center } };

	// 剛体の作成
	btRigidBody* box = new btRigidBody{ mass, state, shape, center };

	// 空間に追加
	world->addRigidBody(box);

	// システムループ
	while (System::Update()) {
		ClearPrint();

		// Bulletのステップを進める
		world->stepSimulation(Scene::DeltaTime());

		// 位置の取得
		btVector3 pos = box->getCenterOfMassPosition();

		// 位置の表示
		Print << U"box position: {:.2F}, {:.2F}, {:.2F}"_fmt(pos.x(), pos.y(), pos.z());
	}

	// 剛体を空間から削除
	world->removeRigidBody(box);

	// 剛体の削除
	delete shape;
	delete state;
	delete box;

	// 空間の削除
	delete config;
	delete dispatcher;
	delete broadphase;
	delete solver;
	delete world;
}

```
</details>

実際にこれを動かしてみると画面に
`box position: 0.00, 0.00, 0.00`
と表示され、真ん中の数字がどんどん小さくなっているかと思います。

これは空間に質量 1.0 の箱を置き、箱が落ち続けるだけのプログラムです。

まず、Bulletエンジンで物理演算をするにはシミュレーション空間を設定しないといけません。
それらをしているのが9行目から26行目のところです。
```cpp
// 重力
btVector3 gravity{ 0, -9.8, 0 };

// 衝突検知方法の設定
btCollisionConfiguration* config = new btDefaultCollisionConfiguration{ };
btCollisionDispatcher* dispatcher = new btCollisionDispatcher{ config };

// ブロードフェーズ法の設定
btDbvtBroadphase* broadphase = new btDbvtBroadphase{ };

// 拘束(剛体間リンク)のソルバ設定
btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver{ };

// 空間の作成
btDynamicsWorld* world = new btDiscreteDynamicsWorld{ dispatcher, broadphase, solver, config };

// 重力の設定
world->setGravity(gravity);
```

シミュレーション空間の定義ができたので、次は空間に箱を置きます。
それらをしているのが32行目から48行目です。

```cpp
// 質量
double mass = 1.0;

// 中心位置
btVector3 center{ 0, 0, 0 };

// 形状の設定 (箱)
btCollisionShape* shape = new btBoxShape{ btVector3{ 1, 1, 1 } };

// 初期位置と姿勢
btMotionState* state = new btDefaultMotionState{ btTransform{ btQuaternion::getIdentity(), center } };

// 剛体の作成
btRigidBody* box = new btRigidBody{ mass, state, shape, center };

// 空間に追加
world->addRigidBody(box);
```

これで空間に箱が設置できました！
でもこのままだと箱が動かないのでシミュレーション空間を

```cpp
// Bulletのステップを進める
world->stepSimulation(Scene::DeltaTime());
```

で動かします。
これで箱の設置と物理演算ができます！

以下の`getCenterOfMassPosition`で剛体の中心位置を取得します。

```cpp
// 位置の取得
btVector3 pos = box->getCenterOfMassPosition();
```

受け取る値の型は`btVector3`(Bulletの3次元ベクトル)で、
そのままではSiv3DのPrintで使えないので

```cpp
// 位置の表示
Print << U"box position: {:.2F}, {:.2F}, {:.2F}"_fmt(pos.x(), pos.y(), pos.z());
```

で表示します。

使い終わったら剛体を空間から取り除いてnewした変数をdeleteします。
```cpp
// 剛体を空間から削除
world->removeRigidBody(box);

// 剛体の削除
delete shape;
delete state;
delete box;

// 空間の削除
delete config;
delete dispatcher;
delete broadphase;
delete solver;
delete world;
```

### Siv3Dで描画
このままだとコードが助長になりみにくいので簡単に使えるように適当にライブラリを作ったので
[こちら](https://github.com/tas9n/OpenSiv3DBulletPhysics)からヘッダファイルをコピペして

https://github.com/tas9n/OpenSiv3DBulletPhysics

こちら↓のコードを実行してみましょう！
<details><summary>コード</summary>

```cpp
#include <Siv3D.hpp> // OpenSiv3D v0.6.6
#include "SivBullet.hpp"

void Main() {
	// 3D シーンを描く、マルチサンプリング対応レンダーテクスチャ
	const MSRenderTexture rt{ Scene::Size(), TextureFormat::R8G8B8A8_Unorm_SRGB, HasDepth::Yes };
	// 背景色 (removeSRGBCurve() で sRGB カーブを除去)
	Color bgColor = Palette::Skyblue.removeSRGBCurve();

	// デバッグカメラ
	DebugCamera3D camera{ rt.size(), 60_deg, Vec3{0, 30, -100}, Vec3{0, 0, 0} };

	// シミュレーション空間の定義
	DynamicsWorld world{ Vec3{ 0, -9.8, 0 } };

	// 床
	// 質量 0.0 で { 0, 0, 0 } に { 100, 0.1, 100 } の大きさの箱
	auto ground = world.createBox(Vec3{ 0, 0, 0 }, Vec3{ 100, 0.1, 100 }, 0.0);

	// 箱の配列
	Array<BulletBody> boxes;

	// システムループ
	while (System::Update()) {
		// 空間の更新
		world.update(Scene::DeltaTime());

		// Enterキーで箱を追加
		if (KeyEnter.down()) {
			boxes << world.createBox(Vec3{ Random(-50, 50), 50, Random(-50, 50)}, Vec3{1, 1, 1}, 1.0);
		}

		// カメラの更新
		camera.update();

		// カメラを3D空間に設定
		Graphics3D::SetCameraTransform(camera);

		// [3D 描画]
		{
			const ScopedRenderTarget3D target{ rt.clear(bgColor) };

			// 床の描画
			ground.draw();
			// 箱の描画
			for (const auto& box : boxes) {
				box.draw();
			}
		}

		// [3D を 2D に描画]
		{
			Graphics3D::Flush();
			rt.resolve();
			Shader::LinearToScreen(rt);
		}
	}
}
```
</details>

Enterキーを押すごとにランダムな位置から箱が降ってきます！

`#include "SivBullet.hpp"`でSivBullet.hppをincludeして
DynamicsWorldクラスでシミュレーション空間を定義します。
剛体の作成にはDynamicsWorldクラスのcreate〇〇メソッドを使い、剛体の作成と空間へ追加をしてくれます。
戻り値がBulletBodyクラスになっているので、戻り値をArray<BulletBody>に追加してdrawメソッドでそれぞれの形状を描画しています。

DynamicsWorldやBulletBodyは自動でメモリの開放をしてくれるので、deleteはしなくてOK！

以上！！！！

※ヘッダファイルはBoxやSphereなど、簡単な形の剛体を作って空間に追加するくらいなので
剛体の設定などいろいろは自分で書き足してみてください。

## 参考記事

OpenSiv3Dリファレンス

https://zenn.dev/reputeless/books/siv3d-documentation

Bulletについての記事

http://slis.tsukuba.ac.jp/~fujisawa.makoto.fu/lecture/iml/text/2_bullet.html