この記事はSiv3D Advent Calendar 2021の12月20日の記事です。
注意
The Nature of Codeをまったく読んでない人は本を読んでからこの記事を読むことを勧めますまたProcessingを知らない人も同様です。
The Nature of Codeとは?
タイトルはThe Nature of Code Simulating Natural Systems with Processingで日本語訳はNature of Code Processingではじめる自然現象のシミュレーションとなっています。単にNature of Codeと呼ばれていることが多いと思います。
内容を簡単に紹介すると数学や物理の式(式はそれほど難しくありません)とProcessingを用いて自然界で動くものやフラクタルなどのシステムを記述しています。
目的
The Nature of Codeに記述されている式や法則あるいは自然界でのシミュレーションとModern C++そしてSiv3Dをを少しづつ理解する事が目的です。
原著ではProcessingで書かれているのですがそれらをC++とSiv3Dに移植します。
また参考とする本は日本語ではなく英語で書かれている本をベースとしています。
範囲と対象
基本的にchapter1からchapter10まで実装します。ただしこの記事では時間などの制約からchapter2の一部のみ実装しています、chapter1やそれ以降の章は来年になると思います。
内容を少しづつ理解することが目的なので更新は1か月~2か月程度(数学的背景を深堀りする場合もあるのでそれ以上かかるかも😅)を目安としています。
実装は以下からChapter2の2.4 Dealing with Massから実装しています。
Chapter2 Forces
Forcesの基本となるMoverクラスは以下のように実装しています。
# include <Siv3D.hpp>
class Mover {
private:
Vec2 position;
Vec2 velocity;
Vec2 acceleration;
double mass;
public:
// デフォルトコンストラクターを必ず入れる
Mover() = default;
Mover(const Vec2& position, const double& mass) : position(position), velocity(Vec2(0.0, 0.0)), acceleration(Vec2(0.0, 0.0)), mass(mass) {}
void applyForce(const Vec2& force) {
Vec2 calcForce = force / mass;
acceleration += calcForce;
}
void update() {
velocity += acceleration;
position += velocity;
acceleration *= 0.0;
}
void render() {
double scale = 2.0;
double size = mass * scale;
const ColorF color = ColorF{ 1.0, 1.0, 1.0, 1.0 };
Circle{ position, size }.draw(color);
}
void checkEdge(const Point& windowSize) {
if (position.x < 0) {
position.x = 0;
velocity.x *= -1;
}
else if (position.x > windowSize.x) {
position.x = windowSize.x;
velocity.x *= -1;
}
if (position.y < 0) {
position.y = 0;
velocity.y *= -1;
}
else if (position.y > windowSize.y) {
position.y = windowSize.y;
velocity.y *= -1;
}
}
};
2.4 Dealing with Mass and 2.5 Creating Forces
注意点としてデフォルトコンストラクターを必ず入れないとエラーになります。
void Main()
{
Scene::SetBackground(ColorF{ 0.0, 0.0, 0.0 });
const int32 particleMAX = 10;
const int32 width = Scene::Width();
const int32 height = Scene::Height();
const Point windowSize = Point(width, height);
const Vec2 wind = Vec2(0.05, 0.0);
const Vec2 gravity = Vec2(0.0, 0.1);
std::vector<Mover> movers(particleMAX);
for (int32 count = 0; count < particleMAX; count++) {
const Vec2 position = Vec2(0, 0);
const double mass = Random<double>(5.0, 20.0);
Mover mover = Mover(position, mass);
movers.push_back(mover);
}
std::cout << movers.size() << std::endl;
while (System::Update())
{
for (int32 count = 0; count < movers.size(); count++) {
movers[count].applyForce(wind);
movers[count].applyForce(gravity);
movers[count].update();
movers[count].render();
movers[count].checkEdge(windowSize);
}
}
}
ソースコードはこちらです。
2.6 Gravity on Earth and Modeling a Force
void Main()
{
Scene::SetBackground(ColorF{ 0.0, 0.0, 0.0 });
const int32 particleMAX = 10;
const int32 width = Scene::Width();
const int32 height = Scene::Height();
const Point windowSize = Point(width, height);
std::vector<Mover> movers(particleMAX);
for (int32 count = 0; count < particleMAX; count++) {
const Vec2 position = Vec2(0, 0);
const double mass = Random<double>(5.0, 20.0);
Mover mover = Mover(position, mass);
movers.push_back(mover);
}
std::cout << movers.size() << std::endl;
while (System::Update())
{
for (int32 count = 0; count < movers.size(); count++) {
const double mass = movers[count].mass;
const Vec2 wind = Vec2(0.05, 0.0);
const Vec2 gravity = Vec2(0.0, 0.1 * mass);
movers[count].applyForce(wind);
movers[count].applyForce(gravity);
movers[count].update();
movers[count].render();
movers[count].checkEdge(windowSize);
}
}
}
ソースコードはこちらです。
2.7 Friction
Processingではnormalize
ですがSiv3Dではnormalized
を使ったほうが良いと思います。
void Main()
{
Scene::SetBackground(ColorF{ 0.0, 0.0, 0.0 });
const int32 particleMAX = 10;
const int32 width = Scene::Width();
const int32 height = Scene::Height();
const Point windowSize = Point(width, height);
const float coefficientFriction = 0.01;
std::vector<Mover> movers(particleMAX);
for (int32 count = 0; count < particleMAX; count++) {
const Vec2 position = Vec2(0, 0);
const double mass = Random<double>(5.0, 20.0);
Mover mover = Mover(position, mass);
movers.push_back(mover);
}
//std::cout << movers.size() << std::endl;
while (System::Update())
{
for (int32 count = 0; count < movers.size(); count++) {
const double mass = movers[count].mass;
const Vec2 wind = Vec2(0.05, 0.0);
const Vec2 gravity = Vec2(0.0, 0.1 * mass);
Vec2 friction = movers[count].velocity;
friction *= -1;
// ここはnormalizeではない?
// friction.normalize();
friction.normalized();
friction *= coefficientFriction;
movers[count].applyForce(wind);
movers[count].applyForce(gravity);
movers[count].applyForce(friction);
movers[count].update();
movers[count].render();
movers[count].checkEdge(windowSize);
}
}
}
ソースコードはこちらです。
このあとは?
再度記載しますが今後他の章などを追加します。ただしQiitaは限らないかもしれません。またこの記事で書いたRustとnannouでも実装する予定です。