OpenSiv3Dの2Dパーティクルシステムの使い方

#パーティクルシステムとは
パーティクル・システム（英: particle system）はコンピュータグラフィック技術のひとつで、運動する粒子により構成される事象を表現するために使われる。一般にパーティクル・システムを用いて模擬される事象の例としては、炎、爆発、煙、流水、火花、落葉、雲、霧、雪、埃、流星、毛髪、毛皮、草地、あるいは光跡や呪文の視覚効果などが挙げられる。[Wikipediaより](https://ja.wikipedia.org/wiki/%E3%83%91%E3%83%BC%E3%83%86%E3%82%A3%E3%82%AF%E3%83%AB%E3%83%BB%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0)

公式リファレンスはこちら
https://siv3d.github.io/ja-jp/reference/2d-particle/

![Siv3DAdC2019.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273953/95d07bd7-1996-8fad-3819-1e7b788488cd.gif)

#サンプル
OpenSiv3Dのバージョンは0.4.2です。

これは公式リファレンスのサンプルを自分用に調整したコードです。
[(gist)](https://gist.github.com/falrnd/75d750a225e125369575a1b01f09092d)

```C++
//based on https://siv3d.github.io/ja-jp/reference/2d-particle/
# include <Siv3D.hpp>

const String siv3d_kun_path = U"example/siv3d-kun.png";
Polygon CreateSiv3D_kunPolygon()
{
	return Image(siv3d_kun_path)
		.alphaToPolygonCentered().simplified(1);
}

auto CreateParticleTexture()
{
	Image image{ 128,128 };
	Shape2D::NStar(5, 64, 32, image.size() / 2).asPolygon().overwrite(image, Palette::White);
	return Texture{ image };
};

void Main()
{
	Window::Resize(1280, 720);
	bool debugMode = false;
	bool whiteBackground = false;

	const std::array<BlendState, 4> blends = {
		BlendState::Default, BlendState::Additive, BlendState::Opaque, BlendState::Subtractive
	};
	size_t blendIndex = 0;

	CircleEmitter2D circleEmitter;
	ArcEmitter2D arcEmitter;
	RectEmitter2D rectEmitter;
	PolygonEmitter2D polygonEmitter(CreateSiv3D_kunPolygon());

	enum EmitterType : size_t {
		Circle, Arc, Rect, Polygon,
	};
	size_t emitterIndex = EmitterType::Arc;

	Vec2 position{ 300, 340 };
	Vec2 force{ 0.0, 0.0 };

	ParticleSystem2D particleSystem(position, force);
	particleSystem.setEmitter(arcEmitter);
	particleSystem.setTexture(CreateParticleTexture());
	particleSystem.prewarm();

	ParticleSystem2DParameters parameters;
	HSV startColor{ ColorF(1.0) };

	const Texture texture = Texture(siv3d_kun_path);
	bool drawTexture = false;

	while (System::Update())
	{
		SimpleGUI::CheckBox(debugMode, U"Debug", Vec2(40, 660), 120);
		SimpleGUI::CheckBox(whiteBackground, U"White", Vec2(160, 660), 120);
		SimpleGUI::CheckBox(drawTexture, U"Texture", Vec2(280, 660), 140, (emitterIndex == EmitterType::Polygon));
		Scene::SetBackground(whiteBackground ? Color(250, 252, 255) : Palette::DefaultBackground);

		{//particleSystem,parametersの設定
			const int32 x = 560, labelw = 120, sliderw = 200;
			SimpleGUI::Slider(U"Rate", parameters.rate, 1.0, 500.0, { x, 20 }, labelw, sliderw);
			SimpleGUI::Slider(U"Max", parameters.maxParticles, 50.0, 2500.0, Vec2(x, 60), labelw, sliderw);
			SimpleGUI::Slider(U"LifeTime", parameters.startLifeTime, 0.0, 5.0, Vec2(x, 100), labelw, sliderw);
			SimpleGUI::Slider(U"Speed", parameters.startSpeed, 0.0, 320.0, Vec2(x, 140), labelw, sliderw);
			SimpleGUI::ColorPicker(startColor, Vec2(x, 180));
			SimpleGUI::Slider(U"Size", parameters.startSize, 0.0, 150.0, Vec2(x, 300), labelw, sliderw);
			SimpleGUI::Slider(U"Rotation", parameters.startRotationDeg, -180, 180, Vec2(x, 340), labelw, sliderw);
			SimpleGUI::Slider(U"AngularVel", parameters.startAngularVelocityDeg, -720, 720, Vec2(x, 380), labelw, sliderw);
			SimpleGUI::Slider(U"Force X", force.x, -320.0, 320.0, Vec2(x, 420), labelw, sliderw);
			SimpleGUI::Slider(U"Force Y", force.y, -320, 320.0, Vec2(x, 460), labelw, sliderw);
			SimpleGUI::RadioButtons(blendIndex, { U"Default", U"Additive", U"Opaque", U"Subtractive" }, Vec2(x, 500), 320);

			SimpleGUI::CheckBox(parameters.randomStartRotation, U"randomRotation", Vec2(x, 660), 220);
			SimpleGUI::CheckBox(parameters.randomAngularVelocity, U"randomAngularVelocity", Vec2(x + 200, 660), 280);

			parameters.blendState = blends[blendIndex];
			parameters.startColor = startColor;
			particleSystem.setParameters(parameters);
			particleSystem.setForce(force);

			if (MouseR.pressed())
				particleSystem.setPosition(position = Cursor::Pos());
		}

		{//emitterの設定
			const int32 x = 900, labelw = 120, sliderw = 200;

			bool emitterChanged = false;
			emitterChanged = SimpleGUI::RadioButtons(emitterIndex, { U"Circle", U"Arc", U"Rect", U"Polygon" }, Vec2(x, 20), 360);

			switch (emitterIndex)
			{
			case EmitterType::Circle:
				emitterChanged |= SimpleGUI::Slider(U"Source Radius", circleEmitter.sourceRadius, 0.0, 40.0, Vec2(x, 180), 160, 200);
				emitterChanged |= SimpleGUI::Slider(U"R", circleEmitter.r, 0.0, 320.0, Vec2(x, 220), 160, 200);
				emitterChanged |= SimpleGUI::CheckBox(circleEmitter.randomDirection, U"Random Direction", Vec2(x, 260), 360);
				emitterChanged |= SimpleGUI::CheckBox(circleEmitter.fromShell, U"From Shell", Vec2(x, 300), 300);

				if (emitterChanged) // setEmitter は重い処理なので、変更があった時だけ
					particleSystem.setEmitter(circleEmitter);
				break;

			case EmitterType::Arc:
				emitterChanged |= SimpleGUI::Slider(U"Source Radius", arcEmitter.sourceRadius, 0.0, 40.0, Vec2(x, 180), 160, 200);
				emitterChanged |= SimpleGUI::Slider(U"R", arcEmitter.r, 0.0, 320.0, Vec2(x, 220), 160, 200);
				emitterChanged |= SimpleGUI::Slider(U"Direction", arcEmitter.direction, -180, 180, Vec2(x, 260), 160, 200);
				emitterChanged |= SimpleGUI::Slider(U"Angle", arcEmitter.angle, 0.0, 360, Vec2(x, 300), 160, 200);
				emitterChanged |= SimpleGUI::CheckBox(arcEmitter.randomDirection, U"Random Direction", Vec2(x, 340), 360);
				emitterChanged |= SimpleGUI::CheckBox(arcEmitter.fromShell, U"From Shell", Vec2(x, 380), 360);

				if (emitterChanged) // setEmitter は重い処理なので、変更があった時だけ
					particleSystem.setEmitter(arcEmitter);
				break;

			case EmitterType::Rect:
				emitterChanged |= SimpleGUI::Slider(U"Source Radius", rectEmitter.sourceRadius, 0.0, 40.0, Vec2(x, 180), 160, 200);
				emitterChanged |= SimpleGUI::Slider(U"Width", rectEmitter.width, 0, 720, Vec2(x, 220), 160, 200);
				emitterChanged |= SimpleGUI::Slider(U"Height", rectEmitter.height, 0, 720, Vec2(x, 260), 160, 200);
				emitterChanged |= SimpleGUI::CheckBox(rectEmitter.randomDirection, U"Random Direction", Vec2(x, 300), 360);
				emitterChanged |= SimpleGUI::CheckBox(rectEmitter.fromShell, U"From Shell", Vec2(x, 340), 360);

				if (emitterChanged) // setEmitter は重い処理なので、変更があった時だけ
					particleSystem.setEmitter(rectEmitter);
				break;

			case EmitterType::Polygon:
				if (emitterChanged) // setEmitter は重い処理なので、変更があった時だけ
					particleSystem.setEmitter(polygonEmitter);
				break;
			}
		}

		particleSystem.update();

		if (debugMode)
			particleSystem.drawDebug();
		else
			particleSystem.draw();

		if (emitterIndex == EmitterType::Polygon && drawTexture)
			texture.drawAt(position);

		ClearPrint();
		Print << U"{} particles"_fmt(particleSystem.num_particles());
	}
}
```

#使い方
大まかな流れとしては、
1. ParticleSystem2Dクラスのインスタンスを作る
2. 各種設定をする
3. ゲームループ内でupdate()やdraw()などを呼び出す
といった感じです。

##各種設定
###ParticleSystem2D
| 項目        | 型                        | 説明            |
|:------------|:-------------------------|:-----------------|
|setPosition  |Vec2                      |パーティクル源の位置|
|setForce     |Vec2                      |パーティクルにかかる加速度|
|setEmitter   |Emitter                   |後述|
|setTexture   |Texture                   |パーティクルのテクスチャ|
|setParameters|ParticleSystem2DParameters|後述|
パーティクルのテクスチャを変更したときの図
![20191121-191658-123.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273953/83e9510e-e8dc-5e7d-004a-727d69055683.png)

###ParticleSystem2DParameters
| 項目                   | 型                    | 説明 |
|:----------------------|:----------------------|:----|
|rate					|double					|パーティクルの生成頻度[/秒]|
|maxParticles			|double					|パーティクルの生成数(古いものから消える)|
|startLifeTime			|double					|パーティクルの生存時間[秒]|
|startSpeed				|double					|パーティクルの初速|
|startColor				|ColorF					|パーティクルの初期の色|
|colorOverLifeTimeFunc	|Float4(Float4,float,float)|色(初期色(ColorF),総寿命,残り寿命)|
|startSize				|double					|パーティクルの初期のサイズ|
|sizeOverLifeTimeFunc	|float(float,float,float)|サイズ(初期サイズ,総寿命,残り寿命)|
|startRotationDeg		|double					|パーティクルの初期の回転[度]|
|randomStartRotation    |bool					|↑をランダムにする(±180の間)|
|startAngularVelocityDeg|double					|パーティクルの回転速度[度/sec]|
|randomAngularVelocity  |bool					|↑をランダムにする(±Abs(↑の値)の間)|
|blendState				|BlendState				|ブレンドステート(加算合成とか)|

###Emitter
パーティクル源の形状などに関する部分です。現在

* CircleEmitter2D 
* ArcEmitter2D
* RectEmitter2D
* PolygonEmitter2D

の4種があります。

Polygon以外のEmitterでできる設定

| 項目                   | 型                    | 説明 |
|:----------------------|:----------------------|:----|
|各図形の設定				|----	|大きさや角度など|
|randomDirection		|bool	|trueのときはランダム falseのときパーティクルの発生方向が図形の中心→外側の方向になる|
|fromShell				|bool	|trueのときパーティクル源の芯が図形のふちになる falseのときは図形全体|

4種のEmitter全てでできる設定

| 項目                   | 型                    | 説明 |
|:----------------------|:----------------------|:----|
|sourceRadius			|double|パーティクル源の丸みの半径|