次期 Siv3D 用 3D 物理演算機能の紹介

[Siv3D AdventCalendar 2017](https://qiita.com/advent-calendar/2017/siv3d) 2 日目の記事です．

# 概要
本記事では，次期 Siv3D である OpenSiv3D の 3D 機能に向けた，物理演算機能のプロトタイプを紹介します．

![hako.gif](https://qiita-image-store.s3.amazonaws.com/0/34295/29df5cf9-f895-e816-9053-e1b0d8efb378.gif)

```cpp:Main.cpp
# include <Siv3D.hpp>
# include <HamFramework.hpp>

void Main()
{
	Physics3DWorld world;
	Physics3DBody ground = world.createBox(Vec3(0, -50, 0), Vec3(50, 50, 50), 0.0);

	Array<Physics3DBody> bodies;

	for (int y = 0; y < 10; y++)
	{
		for (int z = 0; z < 10; z++)
		{
			for (int x = 0; x < 10; x++)
			{
				const Vec3 pos(x, 10 + y, z);

				bodies.push_back(world.createBox(pos, Vec3::One * 0.5, 1.0));
			}
		}
	}


	while (System::Update())
	{
		world.update();

		ground.draw();

		for (const auto& body : bodies)
		{
			body.draw();
		}
	}
}
```

# 3D 物体の扱いと物理演算
C++で 3D のゲームやアプリケーションなどを開発する際に，3D 物体の扱いには苦労した経験はありませんか．3D の物体同士の相互作用には，面倒な衝突判定・幾何学計算・数値計算を行う必要があります．この面倒さを解決する手段として，3D 物理演算ライブラリが使用される場合があります．3D 物理演算ライブラリとは，3D の形状同士の衝突判定や，物理的な挙動の計算をサポートする機能群であり，[Open Dynamics Engine(ODE)](http://www.ode.org/)，[Havok Physics](https://www.havok.com/)，[Bullet Physics](http://bulletphysics.org/wordpress/) などが知られています．現在は次期 Siv3D である OpenSiv3D に向けて，短い C++ ソースコードで 3D 物理演算 (Bullet Physics) を利用できる機能を開発しています．本記事では物理演算機能のプロトタイプを紹介します．

* OpenSiv3Dの3D機能に搭載予定で現時点では使用することはできません．
* 本プロトタイプは Siv3D August 2016 で開発しています．

## 物体の生成

![box.gif](https://qiita-image-store.s3.amazonaws.com/0/34295/66816412-86d3-aab3-d6ff-ebb0a004ff58.gif)


```cpp:Main.cpp
# include <Siv3D.hpp>
# include <HamFramework.hpp>

void Main()
{
	Physics3DWorld world;
	Physics3DBody ground = world.createBox(Vec3(0, -50, 0), Vec3(50, 50, 50), 0.0);

	Physics3DBody body = world.createBox({0.0, 5.0, 0.0}, Vec3::One * 0.5, 1.0);

	while (System::Update())
	{
		world.update();

		ground.draw();

		body.draw();
	}
}

```

## マウスとのインタラクション

![mouse.gif](https://qiita-image-store.s3.amazonaws.com/0/34295/4bae0f85-da92-2eca-7ca1-735c1dd8c07b.gif)

```cpp:Main.cpp
# include <Siv3D.hpp>
# include <HamFramework.hpp>

void Main()
{
	Physics3DWorld world;
	Physics3DBody ground = world.createBox(Vec3(0, -50, 0), Vec3(50, 50, 50), 0.0);

	Array<Physics3DBody> bodies;

	bodies.push_back(world.createBox(RandomVec3({ -1.0, 1.0 }, { 0.0, 1.0 }, { -1.0, 1.0 }), Vec3::One * 0.5, 1.0));
	bodies.push_back(world.createSphere(RandomVec3({ -1.0, 1.0 }, { 0.0, 1.0 }, { -1.0, 1.0 }), 0.5, 1.0));
	bodies.push_back(world.createCylinder(RandomVec3({ -1.0, 1.0 }, { 0.0, 1.0 }, { -1.0, 1.0 }), 0.5, 1.0, 1.0));
	bodies.push_back(world.createCapsule(RandomVec3({ -1.0, 1.0 }, { 0.0, 1.0 }, { -1.0, 1.0 }), 0.5, 1.0, 1.0));
	
	Physics3DCompoundShape compound;

	compound.addShape(Physics3DCapsuleShape(0.3, 2.0));
	compound.addShape(Physics3DCapsuleShape(0.3, 2.0), Vec3(0.0, 1.0, 0.0), Quaternion(Vec3(0.0, 0.0, 1.0), Math::HalfPi));
	compound.addShape(Physics3DCapsuleShape(0.3, 2.0), Vec3(0.0, -1.0, 0.0), Quaternion(Vec3(1.0, 0.0, 0.0), Math::HalfPi));
	
	bodies.push_back(world.createBody(compound, RandomVec3({ -1.0, 1.0 }, { 0.0, 1.0 }, { -1.0, 1.0 }), 1.0));

	Physics3DMousePickManager mousePick;

	while (System::Update())
	{
		mousePick.update(world);

		world.update();

		ground.draw();

		for (const auto& body : bodies)
		{
			body.draw();
		}

		mousePick.draw();
	}
}

```


## 拘束条件

![hashi.gif](https://qiita-image-store.s3.amazonaws.com/0/34295/5c6447d2-5e7c-093a-3ac6-d8d988818761.gif)

```cpp:Main.cpp
# include <Siv3D.hpp>
# include <HamFramework.hpp>

void Main()
{
	Physics3DWorld world;
	Physics3DBody ground = world.createBox(Vec3(0, -50, 0), Vec3(50, 50, 50), 0.0);

	Array<Physics3DBody> bodies;
	Array<Physics3DPointToPointConstraint> constraints;

	const double plankWidth = 0.4;
	const double plankHeight = 0.2;
	const double plankBreadth = 1.0;
	const double plankOffset = plankWidth;
	const double bridgeWidth = plankWidth * 10.0 + plankOffset*(10.0 - 1.0);
	const double bridgeHeight = 5.0;
	const double halfBridgeWidth = bridgeWidth*0.5;
	const double mass = 1.0;
	const int lastBoxIndex = 10 - 1;

	for (int i = 0; i < 10; ++i)
	{
		double t = static_cast<double>(i) / lastBoxIndex;
		t = -(t * 2.0 - 1.0) * halfBridgeWidth;
		bodies.push_back(world.createBox(Vec3(t, bridgeHeight, 0), Vec3(plankWidth, plankHeight, plankBreadth), (i == 0 || i == lastBoxIndex) ? 0.0 : mass));
	}

	for (int i = 0; i<10 - 1; ++i)
	{
		constraints.push_back(world.createPointToPointConstraint(bodies[i], bodies[i + 1], Vec3(-0.5, 0, -0.5), Vec3(0.5, 0, -0.5)));
		constraints.push_back(world.createPointToPointConstraint(bodies[i], bodies[i + 1], Vec3(-0.5, 0, 0.5), Vec3(0.5, 0, 0.5)));
	}

	while (System::Update())
	{
		if (Input::MouseL.clicked)
		{
			bodies.push_back(world.createSphere(Vec3(Random(-1.0, 1.0), 10.0 + Random(-1.0, 1.0), Random(-1.0, 1.0)), 0.5, 1.0));
		}

		world.update();

		ground.draw();

		for (const auto& body : bodies)
		{
			body.draw();
		}
	}
}
```



# 応用例
## ギア

![gear.gif](https://qiita-image-store.s3.amazonaws.com/0/34295/2ba71915-1130-2616-5b7d-bb3116bb01c8.gif)

## 車

![kuruma.gif](https://qiita-image-store.s3.amazonaws.com/0/34295/e8d8b0bd-6cfd-dd92-35be-edd8386768dc.gif)



# おわりに

本記事では，次期 Siv3D である OpenSiv3D の 3D 機能に向けた，物理演算機能のプロトタイプを紹介しました．本機能により，より多くの 3D ゲームや 3D アプリケーションなどが開発されることを期待しています．