Open
35

OpenSiv3D v0.6 サンプルメモ

現在開発中の OpenSiv3D v0.6 リリース時用のサンプル置き場。
サンプルのビルド・実行には v0.6 の 自前ビルド が必要です。

KDTree

  • v0.4.3 よりも短いコードで書けるように
# include <Siv3D.hpp> // OpenSiv3D v0.6

struct Unit
{
	Circle circle;

	ColorF color;

	void draw() const
	{
		circle.draw(color);
	}
};

// Unit を KDTree で扱えるようにするためのアダプタ
struct UnitAdapter : KDTreeAdapter<Array<Unit>, Vec2>
{
	static const element_type* GetPointer(const point_type& point)
	{
		return point.getPointer();
	}

	static element_type GetElement(const dataset_type& dataset, size_t index, size_t dim)
	{
		return dataset[index].circle.center.elem(dim);
	}
};

void Main()
{
	// 200 個の Unit を生成
	Array<Unit> units;
	{
		for (size_t i = 0; i < 200; ++i)
		{
			Unit unit
			{
				.circle = Circle{ RandomVec2(Scene::Rect()), 4 },
				.color = RandomColorF()
			};
			units << unit;
		}
	}

	// kd-tree を構築
	KDTree<UnitAdapter> kdTree{ units };

	// radius search する際の探索距離
	constexpr double searchDistance = 80.0;

	while (System::Update())
	{
		const Vec2 cursorPos = Cursor::PosF();

		Circle{ cursorPos, searchDistance }.draw(ColorF{ 1.0, 0.2 });

		// searchDistance 以内の距離にある Unit のインデックスを取得
		for (auto index : kdTree.radiusSearch(cursorPos, searchDistance))
		{
			Line{ cursorPos, units[index].circle.center }.draw(4);
		}

		// ユニットを描画
		for (const auto& unit : units)
		{
			unit.draw();
		}
	}
}

Spline2D

https://twitter.com/Reputeless/status/1347359283021664258
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	Window::Resize(1280, 720);
	Scene::SetBackground(ColorF{ 0.75 });

	Array<Vec2> points;
	Spline2D spline;

	Polygon polygon;
	Stopwatch stopwatch;
	SplineIndex si;

	while (System::Update())
	{
		// 制御点の追加
		if (MouseL.down())
		{
			points << Cursor::Pos();
			spline = Spline2D{ points, CloseRing::Yes };
			polygon = spline.calculateRoundBuffer(24);
			stopwatch.restart();
		}

		// 各区間の Bounding Rect の可視化
		for (size_t i = 0; i < spline.size(); ++i)
		{
			const ColorF color = Colormap01F(i / 18.0);
			spline.boundingRect(i)
				.draw(ColorF{ color, 0.1 })
				.drawFrame(1, 0, ColorF{ color, 0.5 });
		}

		// 点を追加してから 1 秒間は三角形分割を表示
		if (stopwatch.isRunning()
			&& (stopwatch < 1s))
		{
			polygon.drawWireframe(1, ColorF{ 0.25, (1.0 - stopwatch.sF()) });
			polygon.draw(ColorF{ 0.4, stopwatch.sF() });
		}
		else
		{
			polygon.draw(ColorF{ 0.4 });
			// 曲率に応じた色でスプラインを描画
			spline.draw(10, [&](SplineIndex si) { return Colormap01F(spline.curvature(si) * 24); });
		}

		// 制御点の表示
		for (const auto& point : points)
		{
			Circle{ point, 8 }.drawFrame(2, ColorF{ 0.8 });
		}

		// スプライン上を移動する物体
		if (spline)
		{
			si = spline.advanceWrap(si, Scene::DeltaTime() * 400);
			Circle{ spline.position(si), 20 }.draw(HSV{ 145, 0.9, 0.95 });
		}
	}
}

長方形詰込み(回転許可)

https://twitter.com/Reputeless/status/1348610384551235587
# include <Siv3D.hpp> // OpenSiv3D v0.6

// ランダムな長方形の配列を作成
Array<Rect> GenerateRandomRects()
{
    Array<Rect> rects(Random(4, 32));

    for (auto& rect : rects)
    {
        const Point pos = RandomPoint(Rect{ Scene::Size() - Size{ 150, 150 } });
        rect.set(pos, Random(20, 150), Random(20, 150));
    }

    return rects;
}

void Main()
{
    Window::Resize(1280, 720);
    Scene::SetBackground(ColorF{ 0.99 });
    Array<Rect> input;
    Array<double> rotations;
    RectanglePack output;
    Point offset{ 0, 0 };
    Stopwatch s;

    while (System::Update())
    {
        if (!s.isStarted() || s > 1.8s)
        {
            input = GenerateRandomRects();
            rotations.resize(input.size());
            rotations.fill(0.0);
            output = RectanglePacking::Pack(input, 1024, AllowFlip::Yes);

            for (size_t i = 0; i < input.size(); ++i)
            {
                if (input[i].w != output.rects[i].w)
                {
                    rotations[i] = 270_deg;
                }
            }

            // 画面中央に表示するよう位置を調整
            offset = (Scene::Size() - output.size) / 2;
            for (auto& rect : output.rects)
            {
                rect.moveBy(offset);
            }

            s.restart();
        }

        // アニメーション
        const double k = Min(s.sF() * 10, 1.0);
        const double t = Math::Saturate(s.sF() - 0.2);
        const double e = EaseInOutExpo(t);

        Rect{ offset, output.size }.draw(ColorF{ 0.7, e });

        for (auto i : step(input.size()))
        {
            const RectF in = input[i].scaledAt(input[i].center(), k);
            const RectF out = output.rects[i];
            const Vec2 center = in.center().lerp(out.center(), e);           
            const RectF rect{ Arg::center = center, in.size };

            rect.rotatedAt(rect.center(), Math::Lerp(0.0, rotations[i], e))
                .draw(HSV{ i * 25.0, 0.65, 0.9 })
                .drawFrame(2, 0, ColorF{ 0.25 });
        }
    }
}

Subdivision2D

  • v0.4.3 よりも短いコードで書けるように
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	Window::Resize(1280, 720);

	constexpr Size size{ 1280, 720 };
	constexpr Rect rect{ size };
	Subdivision2D subdiv{ rect };

	for (const PoissonDisk2D pd{ size, 40 }; 
		const auto& point : pd.getPoints())
	{
		if (rect.contains(point))
		{
			subdiv.addPoint(point);
		}
	}

	const Array<Polygon> facetPolygons = subdiv
		.calculateVoronoiFacets()
		.map([rect](const VoronoiFacet& f)
	{
		return Geometry2D::And(Polygon{ f.points }, rect).front();
	});

	while (System::Update())
	{
		for (auto&& [i, facetPolygon] : Indexed(facetPolygons))
		{
			facetPolygon
				.draw(HSV{ i * 25.0, 0.65, 0.8 })
				.drawFrame(2, ColorF{ 0.25 });
		}
	}
}

https://twitter.com/Reputeless/status/1351003604174438407
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	Window::Resize(1280, 720);
	Scene::SetBackground(ColorF{ 0.8, 0.9, 0.8 });

	Array<Vec2> points;
	Polygon polygon;
	LineString path;

	constexpr NavMeshConfig config{ .agentRadius = 20.0 };
	NavMesh navMesh;

	while (System::Update())
	{
		if (MouseL.down())
		{
			points << Cursor::Pos();
			polygon = Spline2D{ points }.calculateRoundBuffer(24, 8, 12);
			navMesh.build(polygon, config);
			path = navMesh.query(points.front(), points.back());
		}

		polygon.draw(ColorF{ 1.0 }).drawFrame(2, ColorF{ 0.7 });

		if (path)
		{
			path.draw(8, ColorF{ 0.1, 0.5, 0.9 });
			path.front().asCircle(12).draw(ColorF{ 1.0, 0.3, 0.0 });
			path.back().asCircle(12).draw(ColorF{ 1.0, 0.3, 0.0 });
		}
	}
}

LineString::calculatePointFromOrigin()

https://twitter.com/Reputeless/status/1351029344957771779
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	Window::Resize(1280, 720);
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	LineString points;
	Polygon polygon;

	double distanceFromOrigin = 0.0;
	double length = 0.0;

	while (System::Update())
	{
		if (MouseL.down())
		{
			points << Cursor::Pos();
			polygon = points.calculateRoundBuffer(20);
			length = points.calculateLength();
		}

		polygon.draw().drawFrame(2, ColorF{ 0.7 });
		points.draw(2, ColorF{ 0.75 });

		if (2 < points.size() && length)
		{
			distanceFromOrigin += (Scene::DeltaTime() * 800);

			if (length < distanceFromOrigin)
			{
				distanceFromOrigin = Math::Fmod(distanceFromOrigin, length);
			}

			const Vec2 position = points.calculatePointFromOrigin(distanceFromOrigin);
			position.asCircle(20).draw(ColorF{ 0.5 });
		}
	}
}

Polygon::outline()

https://twitter.com/Reputeless/status/1351193928104636417
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	Window::Resize(1280, 720);
	Scene::SetBackground(ColorF{ 0.15 });

	const Polygon polygon0 = Shape2D::Plus(180, 100, Scene::Center().movedBy(-350, -120));
	const Polygon polygon1 = Shape2D::Heart(180, Scene::Center().movedBy(0, 120));
	const Polygon polygon2 = Shape2D::NStar(8, 180, 140, Scene::Center().movedBy(350, -120));

	while (System::Update())
	{
		const double t = (Scene::Time() * 720);

		polygon0.draw(ColorF{ 0.4 });
		polygon0.outline(t, 200).draw(LineStyle::RoundCap, 8, ColorF{ 0, 1, 0.5 });

		polygon1.draw(ColorF{ 0.4 });
		polygon1.outline(t, 200).draw(LineStyle::RoundCap, 8, ColorF{ 0, 1, 0.5 });
		
		polygon2.draw(ColorF{ 0.4 });
		polygon2.outline(t, 200).draw(LineStyle::RoundCap, 8, ColorF{ 0, 1, 0.5 });
	}
}

https://twitter.com/Reputeless/status/1351720508153188352
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	Window::Resize(1280, 720);
	Scene::SetBackground(ColorF{ 0.1, 0.2, 0.2 });

	constexpr Size size{ 1280, 720 };
	constexpr Rect rect{ size };
	Subdivision2D subdiv{ rect };

	for (const PoissonDisk2D pd{ size, 40 };
		const auto & point : pd.getPoints())
	{
		if (rect.contains(point))
		{
			subdiv.addPoint(point);
		}
	}

	const Array<Polygon> facetPolygons = subdiv
		.calculateVoronoiFacets()
		.map([rect](const VoronoiFacet& f)
			{
				return Geometry2D::And(Polygon{ f.points }, rect).front();
			});

	Polygon mesh;
	NavMesh navMeshL, navMeshS;
	LineString pathL, pathS;
	constexpr double agentRadiusL = 30, agentRadiusS = 10;
	const Vec2 lStart{ 140, 120 };
	const Vec2 sStart{ 100, 200 };
	const Vec2 goal{ 1100, 300 };
	const Polygon goalDiamond = RectF{ Arg::center = goal, 48 }.rotated(45_deg).calculateRoundBuffer(3);

	while (System::Update())
	{
		for (const auto& facetPolygon : facetPolygons)
		{
			facetPolygon
				.draw(ColorF{ 0.3 })
				.drawFrame(2, ColorF{ 0.25 });
		}

		if (MouseL.pressed())
		{
			const Vec2 pos = Cursor::Pos();

			for (const auto& facetPolygon : facetPolygons)
			{
				if (facetPolygon.intersects(pos))
				{
					if (mesh.isEmpty())
					{
						mesh = facetPolygon;
					}
					else
					{
						mesh.append(facetPolygon);
						navMeshL.build(mesh, NavMeshConfig{ .agentRadius = agentRadiusL });
						navMeshS.build(mesh, NavMeshConfig{ .agentRadius = agentRadiusS });
						pathL = navMeshL.query(lStart, goal);
						pathS = navMeshS.query(sStart, goal);
					}

					break;
				}
			}
		}

		mesh.draw(ColorF{ 0.9, 0.8, 0.6 }).drawFrame(6, ColorF{ 0.5, 0.3, 0.0 });

		lStart.asCircle(agentRadiusL).draw(ColorF{ 0.2, 0.6, 0.5 });
		pathL.draw(6, ColorF{ 0.2, 0.6, 0.5 });

		sStart.asCircle(agentRadiusS).draw(ColorF{ 0.2, 0.3, 0.5 });
		pathS.draw(6, ColorF{ 0.2, 0.3, 0.5 });

		goalDiamond.draw(ColorF{ 0.9, 0.2, 0.0 });
	}
}

Graphics2D::DrawTriangles()

https://twitter.com/Reputeless/status/1352991430822662144
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	Window::Resize(1280, 720);
	Scene::SetBackground(ColorF{ 0.8, 0.9, 1.0 });
	
	const VertexShader vs
		= HLSL{ U"example/shader/hlsl/soft_shape.hlsl" }
		| GLSL{ U"example/shader/glsl/soft_shape.vert", { { U"VSConstants2D", 0 }, { U"SoftShape", 1 } }}
		| MSL{ U"VS_SoftShape" };

	if (!vs)
	{
		throw(U"Failed to load shader file.");
	}

	ConstantBuffer<float> cb;

	while (System::Update())
	{
		cb = static_cast<float>(Scene::Time());
		Graphics2D::SetConstantBuffer(ShaderStage::Vertex, 1, cb);
		
		{
			ScopedCustomShader2D shader{ vs };
			Graphics2D::DrawTriangles(360);
		}
	}
}

SVG

https://twitter.com/Reputeless/status/1355349452416897027
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	const Grid<bool> qr = QR::EncodeText(U"https://github.com/Siv3D/OpenSiv3D/tree/v6_winmac_develop");	
	const SVG svg = QR::MakeSVG(qr);
	const Texture t1{ svg.render(100, 100) };
	const Texture t2{ svg.render(200, 200) };
	const Texture t3{ svg.render(800, 800) };

	while (System::Update())
	{
		t1.draw();
		t2.draw(120, 0);
		t3.draw(340, 0);
	}
}

Effect の再帰

https://twitter.com/Reputeless/status/1355519950551609346
# include <Siv3D.hpp>

// 重力加速度
constexpr Vec2 Gravity{ 0, 240 };

// 火花の状態
struct Fire
{
	// 初速
	Vec2 v0;

	// 色相のオフセット
	double hueOffset;

	// スケーリング
	double scale;

	// 破裂するまでの時間
	double nextFireSec;

	// 破裂して子エフェクトを作成したか
	bool hasChild = false;
};

// 火花エフェクト
struct Firework : IEffect
{
	// 火花の個数
	static constexpr int32 FireCount = 12;

	// 循環参照を避けるため、IEffect の中で Effect を持つ場合、参照またはポインタにすること
	const Effect& m_parent; 
	
	// 花火の中心座標
	Vec2 m_center;
		
	// 火の状態
	std::array<Fire, FireCount> m_fires;

	// 何世代目? [0, 1, 2]
	int32 m_n;

	Firework(const Effect& parent, const Vec2& center, int32 n, const Vec2& v0)
		: m_parent{ parent }
		, m_center{ center }
		, m_n{ n }
	{
		for (auto i : step(FireCount))
		{
			const double angle = (i * 30_deg + Random(-10_deg, 10_deg));
			const double speed = (60.0 - m_n * 15) * Random(0.9, 1.1) * (IsEven(i) ? 0.5 : 1.0);
			m_fires[i].v0			= Circular{ speed, angle } + v0;
			m_fires[i].hueOffset	= Random(-10.0, 10.0) + (IsEven(i) ? 15 : 0);
			m_fires[i].scale		= Random(0.8, 1.2);
			m_fires[i].nextFireSec	= Random(0.7, 1.0);
		}
	}

	bool update(double t) override
	{
		for (const auto& fire : m_fires)
		{
			const Vec2 pos = m_center + fire.v0 * t + 0.5 * t * t * Gravity;
			pos.asCircle((10 - (m_n * 3)) * ((1.5 - t) / 1.5) * fire.scale)
				.draw(HSV{ 10 + m_n * 120.0 + fire.hueOffset, 0.6, 1.0 - m_n * 0.2 });
		}

		if (m_n < 2) // 0, 1 世代目なら
		{
			for (auto& fire : m_fires)
			{
				if (!fire.hasChild && (fire.nextFireSec <= t))
				{
					// 子エフェクトを作成
					const Vec2 pos = m_center + fire.v0 * t + 0.5 * t * t * Gravity;
					m_parent.add<Firework>(m_parent, pos, (m_n + 1), fire.v0 + (t * Gravity));
					fire.hasChild = true;
				}
			}
		}

		return (t < 1.5);
	}
};

// 打ち上げエフェクト
struct FirstFirework : IEffect
{
	// 循環参照を避けるため、IEffect の中で Effect を持つ場合、参照またはポインタにすること
	const Effect& m_parent;

	// 打ち上げ位置
	Vec2 m_start;

	// 打ち上げ初速
	Vec2 m_v0;

	FirstFirework(const Effect& parent, const Vec2& start, const Vec2& v0)
		: m_parent{ parent }
		, m_start{ start }
		, m_v0{ v0 } {}

	bool update(double t) override
	{
		const Vec2 pos = m_start + m_v0 * t + 0.5 * t * t * Gravity;
		Circle{ pos, 6 }.draw();
		Line{ m_start, pos }.draw(LineStyle::RoundCap, 8, ColorF{ 0.0 }, ColorF{ 1.0 - (t / 0.6) });

		if (t < 0.6)
		{
			return true;
		}
		else
		{
			// 終了間際に子エフェクトを作成
			const Vec2 velocity = m_v0 + t * Gravity;
			m_parent.add<Firework>(m_parent, pos, 0, velocity);
			return false;
		}
	}
};

void Main()
{
	Window::Resize(1280, 720);
	Effect effect;

	while (System::Update())
	{
		Scene::Rect().draw(Arg::top(0.0), Arg::bottom(0.2, 0.1, 0.4));

		if (MouseL.down())
		{
			effect.add<FirstFirework>(effect, Cursor::Pos(), Vec2{ 0, -500 });
		}

		{
			ScopedRenderStates2D blend{ BlendState::Additive };
			effect.update();
		}
	}
}

OutlineGlyph

https://twitter.com/Reputeless/status/1356970822842281985
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	Window::Resize(1280, 720);
	Scene::SetBackground(ColorF{ 0.99, 0.96, 0.93 });

	const Font font{ 130, U"example/font/RocknRoll/RocknRollOne-Regular.ttf" };
	const Array<OutlineGlyph> glyphs = font.renderOutlines(U"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!?");

	while (System::Update())
	{
		const double t = Periodic::Sawtooth0_1(2.6s);
		const double len = Periodic::Sine0_1(16s) * 0.5;
		constexpr Vec2 basePos{ 70, 0 };
		Vec2 penPos{ basePos };

		for (const auto& glyph : glyphs)
		{
			const Transformer2D tr{ Mat3x2::Translate(penPos + glyph.getOffset()) };

			for (const auto& ring : glyph.rings)
			{
				const double length = ring.calculateLength(CloseRing::Yes);
				LineString z1 = ring.extractLineString(t * length, length * len, CloseRing::Yes);
				const LineString z2 = ring.extractLineString((t + 0.5) * length, length * len, CloseRing::Yes);
				z1.append(z2.reversed()).drawClosed(3, ColorF{ 0.25 });
			}

			if (penPos.x += glyph.xAdvance;
				1120 < penPos.x)
			{
				penPos.x = basePos.x;
				penPos.y += font.fontSize();
			}
		}
	}
}

JSON

https://twitter.com/Reputeless/status/1358308026701160449
# include <Siv3D.hpp> // OpenSiv3D v0.6

JSON MakeJSON()
{
	JSON json;
	json[U"Window"][U"title"] = U"MyGame";
	json[U"Window"][U"width"] = 1280;
	json[U"Window"][U"height"] = 720;
	json[U"Game"][U"score"] = { 10, 20, 50, 100 };
	return json;
}

void Main()
{
	MakeJSON().save(U"save.json");

	JSON json = JSON::Load(U"save.json");
	json[U"Window"][U"width"] = 1920;
	json[U"Window"][U"height"] = 1080;
	Console << json;

	Console << U"-------------";
	Console << json[U"Window"][U"title"].get<String>();
	Console << json[U"Window"][U"width"].get<int32>();

	Console << U"----";

	// by index
	{
		const size_t size = json[U"Game"][U"score"].size();
		for (size_t i = 0; i < size; ++i)
		{
			Console << json[U"Game"][U"score"][i].get<int32>();
		}
	}

	Console << U"----";

	// range based
	{
		for (const auto& elem : json[U"Game"][U"score"].arrayView())
		{
			Console << elem.get<int32>();
		}
	}

	while (System::Update())
	{

	}
}

FontMethod

https://twitter.com/Reputeless/status/1359465613391892486
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	Window::Resize(1280, 720);
	Scene::SetBackground(ColorF{ 0.99, 0.96, 0.93 });

	const FilePath filePath = U"example/font/RocknRoll/RocknRollOne-Regular.ttf";
	const Font font1{ FontMethod::Bitmap, 48, filePath };
	const Font font2{ FontMethod::SDF, 48, filePath };
	const Font font3{ FontMethod::MSDF, 40, filePath };

	while (System::Update())
	{
		const ColorF textColor{ 0.25 };
		const double size = 140;

		font1(U"Bitmap")
			.draw(size, Vec2{ 20, 20 }, textColor);

		font2(U"SDF")
			.draw(size, Vec2{ 20, 220 }, textColor);

		font3(U"MSDF")
			.draw(size, Vec2{ 20, 420 }, textColor);
	}
}

CJK 対応標準フォント

https://twitter.com/Reputeless/status/1360090518978371584
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	Scene::SetBackground(ColorF{ 0.99, 0.96, 0.93 });

	const Font fontJP{ 60, Typeface::CJK_Regular_JP };
	const Font fontKR{ 60, Typeface::CJK_Regular_KR };
	const Font fontSC{ 60, Typeface::CJK_Regular_SC };
	const Font fontTC{ 60, Typeface::CJK_Regular_TC };
	const Font fontHK{ 60, Typeface::CJK_Regular_HK };

	const String text = U"骨曜喝愛遙扇 - ";

	while (System::Update())
	{
		fontJP(U"こんにちは 你好 안녕하세요")
			.drawBase(20, 60, ColorF{ 0.25 });

		fontJP(text + U"JP").drawBase(20, 160, ColorF{ 0.25 });
		fontKR(text + U"KR").drawBase(20, 260, ColorF{ 0.25 });
		fontSC(text + U"SC").drawBase(20, 360, ColorF{ 0.25 });
		fontTC(text + U"TC").drawBase(20, 460, ColorF{ 0.25 });
		fontHK(text + U"HK").drawBase(20, 560, ColorF{ 0.25 });
	}
}

Material Design Icons

https://twitter.com/Reputeless/status/1363519000974557184
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	const Font font{ 30, Typeface::Medium };
	const Font iconFont{ 30, Typeface::Icon_MaterialDesign };
	font.addFallback(iconFont);

	const Array<String> items = {
		U"\xF00E8  更新",
		U"\xF11E8  編集",
		U"\xF09FA  キーボード設定",
		U"\xF173B  操作方法"
	};

	while (System::Update())
	{
		for (auto&& [i, item] : Indexed(items))
		{
			const RoundRect rect{ 40, 40 + i * 80.0, 320, 60, 8 };

			rect.draw(ColorF{ 0.99, 0.96, 0.93 });

			font(item)
				.draw(55, 48 + i * 80.0, ColorF{ 0.25 });
		}
	}
}

新しい絵文字のサポート

https://twitter.com/Reputeless/status/1364576355052974085
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	Scene::SetBackground(ColorF{ 0.4, 0.5, 0.6 });

	const Texture e0{ U"🪨"_emoji };
	const Texture e1{ U"🧋"_emoji };
	const Texture e2{ U"🪙"_emoji };
	const Texture e3{ U"🪜"_emoji };
	const Texture e4{ U"🪟"_emoji };
	const Texture e5{ U"🪵"_emoji };
	const Texture e6{ U"🪴"_emoji };
	const Texture e7{ U"🪣"_emoji };
	const Texture e8{ U"🪦"_emoji };
	const Texture e9{ U"🪧"_emoji };
	const Texture e10{ U"🪑"_emoji };
	const Texture e11{ U"🧊"_emoji };

	while (System::Update())
	{
		e0.drawAt(100, 100);
		e1.drawAt(300, 100);
		e2.drawAt(500, 100);
		e3.drawAt(700, 100);
		e4.drawAt(100, 300);
		e5.drawAt(300, 300);
		e6.drawAt(500, 300);
		e7.drawAt(700, 300);
		e8.drawAt(100, 500);
		e9.drawAt(300, 500);
		e10.drawAt(500, 500);
		e11.drawAt(700, 500);
	}
}

Buffer2D

https://twitter.com/Reputeless/status/1365327808944246784
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	const Texture texture{ U"🥺"_emoji };

	while (System::Update())
	{
		const double angle = (Scene::Time() * 30_deg);

		Shape2D::Pentagon(60, Vec2{ 100, 100 }, angle)
			.toBuffer2D(Arg::center(100, 100), texture.size())
			.draw(texture);

		Shape2D::Ngon(7, 60, Vec2{ 300, 100 }, angle)
			.toBuffer2D(Arg::center(300, 100), texture.size())
			.draw(texture);

		Shape2D::Plus(60, 30, Vec2{ 500, 100 }, angle)
			.toBuffer2D(Arg::center(500, 100), texture.size())
			.draw(texture);

		Shape2D::Heart(60, Vec2{ 700, 100 }, angle)
			.toBuffer2D(Arg::center(700, 100), texture.size())
			.draw(texture);


		Shape2D::Pentagon(60, Vec2{ 100, 300 }, angle)
			.toBuffer2D(Arg::center(100, 300), texture.size(), angle)
			.draw(texture);

		Shape2D::Ngon(7, 60, Vec2{ 300, 300 }, angle)
			.toBuffer2D(Arg::center(300, 300), texture.size(), angle)
			.draw(texture);

		Shape2D::Plus(60, 30, Vec2{ 500, 300 }, angle)
			.toBuffer2D(Arg::center(500, 300), texture.size(), angle)
			.draw(texture);

		Shape2D::Heart(60, Vec2{ 700, 300 }, angle)
			.toBuffer2D(Arg::center(700, 300), texture.size(), angle)
			.draw(texture);


		Shape2D::Pentagon(60, Vec2{ 100, 500 })
			.toBuffer2D(Arg::center(100, 500), texture.size(), angle)
			.draw(texture);

		Shape2D::Ngon(7, 60, Vec2{ 300, 500 })
			.toBuffer2D(Arg::center(300, 500), texture.size(), angle)
			.draw(texture);

		Shape2D::Plus(60, 30, Vec2{ 500, 500 })
			.toBuffer2D(Arg::center(500, 500), texture.size(), angle)
			.draw(texture);

		Shape2D::Heart(60, Vec2{ 700, 500 })
			.toBuffer2D(Arg::center(700, 500), texture.size(), angle)
			.draw(texture);
	}
}

ImageROI

https://twitter.com/Reputeless/status/1368574068182544392
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	Image image{ U"example/windmill.png" };
	DynamicTexture texture{ image };

	while (System::Update())
	{
		if (MouseL.down())
		{
			// Image 内の指定領域をガウスぼかし
			image(Cursor::Pos().movedBy(-20, -20), 40, 40)
				.gaussianBlur(25);
			
			texture.fill(image);
		}

		texture.draw();
	}
}

Image::warpAffine(), warpPerspective()

https://twitter.com/Reputeless/status/1368922507009433601
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	Window::Resize(1280, 720);

	const Image image1{ U"🐤"_emoji };
	const Image image2{ U"example/windmill.png" };

	const Texture texture1{ image1 };
	const Texture texture2{ image2 };
	
	const Mat3x2 mat = Mat3x2::Rotate(50_deg, image1.size() / 2.0);
	const Texture texture1t{ image1.warpAffine(mat) };

	const Quad q{ Vec2{ 0, 80 }, Vec2{ 400, 0 }, Vec2{ 400, 300 }, Vec2{ 0, 220 } };
	const Texture texture2t{ image2.warpPerspective(q) };

	while (System::Update())
	{
		texture1.draw(0, 0);
		texture2.draw(0, 200);

		texture1t.draw(600, 0).drawFrame(1, 0);
		texture2t.draw(600, 200).drawFrame(1, 0);
	}
}

Mat3x3::Homography()

https://twitter.com/Reputeless/status/1369272962805760002
# include <Siv3D.hpp> // OpenSiv3D v0.6

struct Homography
{
	Float4 m1;
	Float4 m2;
	Float4 m3;
};

// チェッカーパターンの Image を作る
Image MakeCheckerPattern()
{
	Image image{ 1280, 720 , Palette::White };
	for (auto p : step(image.size() / Size{ 40, 40 }))
	{
		if (IsEven(p.x + p.y))
		{
			Rect{ p * 40, 40 }.overwrite(image, Color{ 40 });
		}
	}
	return image;
}

void Main()
{
	Window::Resize(1280, 720);
	Scene::SetBackground(ColorF{ 0.8, 0.9, 1.0 });

	const Texture texture{ U"example/bay.jpg", TextureDesc::Mipped };
	const Texture checker{ MakeCheckerPattern(), TextureDesc::Mipped };

	constexpr double circleR = 12.0;
	const VertexShader vs = HLSL{ U"example/shader/hlsl/homography.hlsl", U"VS" }
		| GLSL{ U"example/shader/glsl/homography.vert", {{ U"VSConstants2D", 0 }, { U"VSHomography", 1} } };
	const PixelShader ps = HLSL{ U"example/shader/hlsl/homography.hlsl", U"PS" }
		| GLSL{ U"example/shader/glsl/homography.frag", {{ U"PSConstants2D", 0 }, { U"PSHomography", 1} } };

	if (!vs || !ps)
	{
		return;
	}

	ConstantBuffer<Homography> vsHomography;
	ConstantBuffer<Homography> psHomography;

	Quad quad{ Vec2{100, 300}, Vec2{500, 300}, Vec2{500, 600}, Vec2{100, 600} };
	Optional<size_t> grabbedIndex;

	bool homography = true;

	while (System::Update())
	{
		SimpleGUI::CheckBox(homography, U"Homography", Vec2{ 40, 40 });

		if (homography)
		{
			ScopedCustomShader2D shader{ vs, ps };
			ScopedRenderStates2D sampler{ SamplerState::ClampAniso };

			{
				const Mat3x3 mat = Mat3x3::Homography(quad.movedBy(580, 0));
				vsHomography = { Float4{ mat._11_12_13, 0 }, Float4{ mat._21_22_23, 0 }, Float4{ mat._31_32_33, 0 } };
				Graphics2D::SetConstantBuffer(ShaderStage::Vertex, 1, vsHomography);

				const Mat3x3 inv = mat.inversed();
				psHomography = { Float4{ inv._11_12_13, 0 }, Float4{ inv._21_22_23, 0 }, Float4{ inv._31_32_33, 0 } };
				Graphics2D::SetConstantBuffer(ShaderStage::Pixel, 1, psHomography);

				// 1x1 の Rect に貼り付けて描くと、適切にホモグラフィ変換される
				Rect{ 1 }(checker).draw();
			}

			{
				const Mat3x3 mat = Mat3x3::Homography(quad);
				vsHomography = { Float4{ mat._11_12_13, 0 }, Float4{ mat._21_22_23, 0 }, Float4{ mat._31_32_33, 0 } };
				Graphics2D::SetConstantBuffer(ShaderStage::Vertex, 1, vsHomography);

				const Mat3x3 inv = mat.inversed();
				psHomography = { Float4{ inv._11_12_13, 0 }, Float4{ inv._21_22_23, 0 }, Float4{ inv._31_32_33, 0 } };
				Graphics2D::SetConstantBuffer(ShaderStage::Pixel, 1, psHomography);

				// 1x1 の Rect に貼り付けて描くと、適切にホモグラフィ変換される
				Rect{ 1 }(texture).draw();
			}
		}
		else
		{
			quad.movedBy(580, 0)(checker).draw();
			quad(texture).draw();
		}

		if (grabbedIndex)
		{
			if (not MouseL.pressed())
			{
				grabbedIndex.reset();
			}
			else
			{
				quad.p(*grabbedIndex).moveBy(Cursor::DeltaF());
			}
		}
		else
		{
			for (auto i : step(4))
			{
				const Circle circle = quad.p(i).asCircle(circleR);

				if (circle.mouseOver())
				{
					Cursor::RequestStyle(CursorStyle::Hand);
				}

				if (circle.leftClicked())
				{
					grabbedIndex = i;
					break;
				}
			}
		}

		for (auto i : step(4))
		{
			quad.p(i).asCircle(circleR).draw(ColorF{ 1.0, 0.3, 0.3, 0.5 });
		}
	}
}

VideoReader

https://twitter.com/Reputeless/status/1369494347335360520
# include <Siv3D.hpp> // OpenSiv3D v0.6

void ShowStatus(const VideoReader& video)
{
	Print << U"file: " << FileSystem::FileName(video.path());
	Print << U"size: " << video.getSize();
	Print << U"fps: " << video.getFPS();
	Print << U"time: {:.2f}s/{:.2f}s"_fmt(video.getPosSec(), video.getLengthSec());
	Print << U"progress: {:.0f} %"_fmt(video.getProgress() * 100.0);
	Print << U"frame: {}/{}"_fmt(video.getCurrentFrameIndex(), video.getFrameCount());
	Print << U"reachedEnd: " << video.reachedEnd();
}

void Main()
{
	Window::Resize(1280, 720);

	VideoReader video{ U"example/video/river.mp4" };

	if (!video)
	{
		throw Error{ U"Failed to load the video" };
	}

	Image frameImage;
	video.readFrame(frameImage);
	DynamicTexture frameTexture{ frameImage };

	const double frameDeltaSec = video.getFrameDeltaSec();
	double frameTimeSec = 0.0;
	bool playing = true;

	while (System::Update())
	{
		ClearPrint();
		ShowStatus(video);

		if (playing)
		{
			frameTimeSec += Scene::DeltaTime();
		}

		if (frameDeltaSec <= frameTimeSec)
		{
			video.readFrame(frameImage);
			frameTexture.fill(frameImage);
			frameTimeSec -= frameDeltaSec;
		}

		frameTexture.draw();

		if (SimpleGUI::Button(U"Reset", Vec2{ 40, 640 }))
		{
			video.setCurrentFrameIndex(0);
			video.readFrame(frameImage);
			frameTexture.fill(frameImage);
			frameTimeSec = 0.0;
		}

		if (SimpleGUI::Button((playing ? U"■" : U"▶"), Vec2{ 160, 640 }))
		{
			playing = !playing;
			frameTimeSec = 0.0;
		}
	}
}

GrabCut

https://twitter.com/Reputeless/status/1369596024310833152
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	Window::Resize(1280, 720);
	Scene::SetBackground(ColorF{ 0.8, 1.0, 0.9 });
	
	const Image image{ U"example/windmill.png" };
	const Texture texture{ image };

	GrabCut grabcut{ image };
	Image mask{ image.size(), Color{0, 0} };
	Image background{ image.size(), Palette::Black };
	Image foreground{ image.size(), Palette::Black };
	Image inpaint;
	DynamicTexture maskTexture{ mask };
	Grid<GrabCutClass> result;
	DynamicTexture classTexture;
	DynamicTexture backgroundTexture{ background };
	DynamicTexture foregroundTexture{ foreground };
	DynamicTexture inpaintTexture{ foreground };

	constexpr Color BackgroundColor{ 0, 0, 255 };
	constexpr Color ForegroundColor{ 250, 100, 50 };

	while (System::Update())
	{
		if (!classTexture || MouseL.up() || MouseR.up())
		{
			grabcut.update(mask, ForegroundColor, BackgroundColor);
			grabcut.getResult(result);
			classTexture.fill(Image(result, [](GrabCutClass c) { return Color(80 * FromEnum(c)); }));

			for (auto p : step(image.size()))
			{
				const bool isBackground = (GrabCutClass::PossibleBackground <= result[p]);

				if (isBackground)
				{
					background[p] = image[p];
					foreground[p] = Color{ 0,0 };
				}
				else
				{
					foreground[p] = image[p];
					background[p] = Color{ 0,0 };
				}
			}

			ImageProcessing::Inpaint(background, background, Color{ 0, 0 }, inpaint);
			inpaint.gaussianBlur(3);

			foregroundTexture.fill(foreground);
			backgroundTexture.fill(background);
			inpaintTexture.fill(inpaint);
		}

		if (MouseL.pressed())
		{
			const Point from = MouseL.down() ? Cursor::Pos() : Cursor::PreviousPos();
			const Point to = Cursor::Pos();
			Line{ from, to }.overwrite(mask, 4, ForegroundColor, Antialiased::No);
			maskTexture.fill(mask);
		}
		else if (MouseR.pressed())
		{
			const Point from = MouseR.down() ? Cursor::Pos() : Cursor::PreviousPos();
			const Point to = Cursor::Pos();
			Line{ from, to }.overwrite(mask, 4, BackgroundColor, Antialiased::No);
			maskTexture.fill(mask);
		}

		texture.draw();
		maskTexture.draw();
		classTexture.draw(600, 0);

		backgroundTexture.scaled(0.7).regionAt(200, 520).draw(ColorF{ 0 });
		backgroundTexture.scaled(0.7).drawAt(200, 520);

		foregroundTexture.scaled(0.7).regionAt(1080, 520).draw(ColorF{ 0 });
		foregroundTexture.scaled(0.7).drawAt(1080, 520);

		inpaintTexture.drawAt(640, 520);
		{
			Transformer2D t{ Mat3x2::Scale(1.1, Vec2{640, 520}.movedBy(0, image.height() / 2)).translated((Scene::Center() - Cursor::Pos()) * 0.04) };
			foregroundTexture.drawAt(640, 520);
		}
	}
}

VideoTexture

https://twitter.com/Reputeless/status/1369973097227374592
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	const VideoTexture videoTexture{ U"example/video/river.mp4", Loop::Yes };

	while (System::Update())
	{
		// 動画の時間を進める (デフォルトでは Scece::DeltaTime() 秒)
		videoTexture.advance();

		videoTexture(400, 400, 300, 200)
			.draw();

		videoTexture
			.scaled(0.5)
			.rotated(Scene::Time() * 30_deg)
			.drawAt(Cursor::Pos());
	}
}

動画編集

# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	VideoReader reader{ U"example/video/river.mp4" };
	VideoWriter writer{ U"output.mp4", reader.getSize(), reader.getFPS() };
	Image frame;

	for (size_t i = 0; i < reader.getFrameCount(); ++i)
	{
		Console << U"{} フレーム目"_fmt(i);
		reader.readFrame(frame);
		writer.writeFrame(frame.grayscale());
	}
}

2D 物理演算 API 刷新

# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	// ウィンドウを 1280x720 にリサイズ
	Window::Resize(1280, 720);

	// 背景色を設定
	Scene::SetBackground(ColorF{ 0.4, 0.7, 1.0 });

	// 2D 物理演算のシミュレーションステップ(秒)
	constexpr double stepSec = (1.0 / 200.0);

	// 2D 物理演算のシミュレーション蓄積時間(秒)
	double accumulatorSec = 0.0;

	// 2D 物理演算のワールド
	P2World world;

	// [_] 地面
	const P2Body ground = world.createLine(P2Static, Vec2{ 0, 0 }, Line{ -1600, 0, 1600, 0 });

	// [■] 箱 (Sleep させておく)
	Array<P2Body> boxes;
	{
		for (auto y : Range(0, 12))
		{
			for (auto x : Range(0, 20))
			{
				boxes << world.createRect(P2Dynamic, Vec2{ x * 50, -50 - y * 100 },
					SizeF{ 50, 100 }, P2Material{ .density = 0.02, .restitution = 0.0, .friction = 1.0 })
					.setAwake(false);
			}
		}
	}

	// 振り子の軸の座標
	constexpr Vec2 pivotPos{ 0, -2400 };

	// チェーンを構成するリンク 1 つの長さ
	constexpr double linkLength = 100.0;

	// チェーンを構成するリンクの数
	constexpr int32 linkCount = 16;

	// チェーンの長さ
	constexpr double chainLength = (linkLength * linkCount);

	// 鉄球の半径
	constexpr double ballR = 200;

	// 鉄球の初期座標
	constexpr Vec2 ballCenter = pivotPos.movedBy(-chainLength - ballR, 0);

	// [●] 鉄球
	const P2Body ball = world.createCircle(P2BodyType::Dynamic, ballCenter, ballR,
		P2Material{ .density = 0.5, .restitution = 0.0, .friction = 1.0 });

	// [ ] 振り子の軸(実体がないプレースホルダー)
	const P2Body pivot = world.createPlaceholder(P2BodyType::Static, pivotPos);

	// [-] チェーンを構成するリンク
	Array<P2Body> links;

	// リンクどうしやリンクと鉄球をつなぐジョイント
	Array<P2PivotJoint> joints;
	{
		for (auto i : step(linkCount))
		{
			// リンクの長方形(隣接するリンクと重なるよう少し大きめに)
			const RectF rect{ Arg::rightCenter = pivotPos.movedBy(i * -linkLength, 0), linkLength * 1.2, 20 };

			// categoryBits を 0 にすることで、箱など他の物体と干渉しないようにする
			links << world.createRect(P2Dynamic, rect.center(), rect.size,
				P2Material{ .density = 0.1, .restitution = 0.0, .friction = 1.0 }, P2Filter{ .categoryBits = 0 });

			if (i == 0)
			{
				// 振り子の軸と最初のリンクをつなぐジョイント
				joints << world.createPivotJoint(pivot, links.back(), rect.rightCenter().movedBy(-linkLength * 0.1, 0));
			}
			else
			{
				// 新しいリンクと、一つ前のリンクをつなぐジョイント
				joints << world.createPivotJoint(links[links.size() - 2], links.back(), rect.rightCenter().movedBy(-linkLength * 0.1, 0));
			}
		}

		// 最後のリンクと鉄球をつなぐジョイント
		joints << world.createPivotJoint(links.back(), ball, pivotPos.movedBy(-chainLength, 0));
	}

	// [/] ストッパー
	P2Body stopper = world.createLine(P2Static, ballCenter.movedBy(0, 200), Line{ -400, 200, 400, 0 });

	// 2D カメラ
	Camera2D camera{ Vec2{ 0, -1200 }, 0.25 };

	while (System::Update())
	{
		for (accumulatorSec += Scene::DeltaTime(); stepSec <= accumulatorSec; accumulatorSec -= stepSec)
		{
			// 2D 物理演算のワールドを更新
			world.update(stepSec);

			// 落下した box は削除
			boxes.remove_if([](const P2Body& body) { return (2000 < body.getPos().y); });
		}

		// 2D カメラの更新
		camera.update();
		{
			// 2D カメラから Transformer2D を作成
			const auto t = camera.createTransformer();

			// 地面を描く
			ground.draw(ColorF{ 0.0, 0.5, 0.0 });

			// チェーンを描く
			for (const auto& link : links)
			{
				link.draw(ColorF{ 0.25 });
			}

			// 箱を描く
			for (const auto& box : boxes)
			{
				box.draw(ColorF{ 0.6, 0.4, 0.2 });
			}

			// ストッパーを描く
			stopper.draw(ColorF{ 0.25 });

			// 鉄球を描く
			ball.draw(ColorF{ 0.25 });
		}

		// ストッパーを無くす
		if (stopper && SimpleGUI::Button(U"Go", Vec2(1100, 20)))
		{
			// ストッパーを破棄
			stopper.release();
		}

		// 2D カメラの操作を描画
		camera.draw(Palette::Orange);
	}
}

P2WheelJoint

# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	// ウィンドウを 1280x720 にリサイズ
	Window::Resize(1280, 720);

	// 背景色を設定
	Scene::SetBackground(ColorF{ 0.4, 0.7, 1.0 });

	// 2D 物理演算のシミュレーションステップ(秒)
	constexpr double stepSec = (1.0 / 200.0);

	// 2D 物理演算のシミュレーション蓄積時間(秒)
	double accumulatorSec = 0.0;

	// 2D 物理演算のワールド
	P2World world;

	// [_] 地面
	Array<P2Body> floors;
	{
		floors << world.createLine(P2Static, Vec2{ 0, 0 }, Line{ -1600, 0, 1600, 0 });

		for (auto i : Range(1, 5))
		{
			if (IsEven(i))
			{
				floors << world.createLine(P2Static, Vec2{ 0, 0 }, Line{ 0, -i * 200, 1600, -i * 200 - 300 });
			}
			else
			{
				floors << world.createLine(P2Static, Vec2{ 0, 0 }, Line{ -1600,  -i * 200 - 300, 0, -i * 200 });
			}
		}
	}

	// [🚙] 車
	const P2Body carBody = world.createRect(P2Dynamic, Vec2{ -1500, -1450 }, SizeF{ 200, 40 });
	const P2Body wheelL = world.createCircle(P2Dynamic, Vec2{ -1550, -1430 }, 30);
	const P2Body wheelR = world.createCircle(P2Dynamic, Vec2{ -1450, -1430 }, 30);
	const P2WheelJoint wheelJointL = world.createWheelJoint(carBody, wheelL, wheelL.getPos(), Vec2{ 0, -1 })
		.setLinearStiffness(4.0, 0.7)
		.setLimits(-5, 5).setLimitsEnabled(true);
	const P2WheelJoint wheelJointR = world.createWheelJoint(carBody, wheelR, wheelR.getPos(), Vec2{ 0, -1 })
		.setLinearStiffness(4.0, 0.7)
		.setLimits(-5, 5).setLimitsEnabled(true);

	// マウスジョイント
	P2MouseJoint mouseJoint;

	// 2D カメラ
	Camera2D camera{ Vec2{ 0, -1200 }, 0.25 };

	while (System::Update())
	{
		for (accumulatorSec += Scene::DeltaTime(); stepSec <= accumulatorSec; accumulatorSec -= stepSec)
		{
			world.update(stepSec);
		}

		// 2D カメラの更新
		camera.update();
		{
			// 2D カメラから Transformer2D を作成
			const auto t = camera.createTransformer();

			if (MouseL.down())
			{
				mouseJoint = world.createMouseJoint(carBody, Cursor::PosF())
					.setMaxForce(carBody.getMass() * 5000.0)
					.setLinearStiffness(2.0, 0.7);
			}
			else if (MouseL.pressed())
			{
				mouseJoint.setTargetPos(Cursor::PosF());
			}
			else if (MouseL.up())
			{
				mouseJoint.release();
			}

			// 地面を描く
			for (const auto& floor : floors)
			{
				floor.draw(ColorF{ 0.0, 0.5, 0.0 });
			}

			carBody.draw(Palette::Gray);
			wheelL.draw(Palette::Gray).drawWireframe(1, Palette::Yellow);
			wheelR.draw(Palette::Gray).drawWireframe(1, Palette::Yellow);

			mouseJoint.draw();
			wheelJointL.draw();
			wheelJointR.draw();
		}

		// 2D カメラの操作を描画
		camera.draw(Palette::Orange);
	}
}

P2WheelJoint (2)

https://twitter.com/Reputeless/status/1375763236981592064
# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	// ウィンドウを 1280x720 にリサイズ
	Window::Resize(1280, 720);

	// 背景色を設定
	Scene::SetBackground(ColorF{ 0.2 });

	// 2D 物理演算のシミュレーションステップ(秒)
	constexpr double stepSec = (1.0 / 200.0);

	// 2D 物理演算のシミュレーション蓄積時間(秒)
	double accumulatorSec = 0.0;

	// 2D 物理演算のワールド
	P2World world;

	const P2Body rail = world.createLineString(P2Static, Vec2{ 0, -400 }, { Vec2{-400, -40}, Vec2{-400, 0}, Vec2{400, 0}, {Vec2{400, -40}} });
	const P2Body wheel = world.createCircle(P2Dynamic, Vec2{ 0, -420 }, 20);
	const P2Body car = world.createCircle(P2Dynamic, Vec2{ 0, -380 }, 10).setFixedRotation(true);	
	
	// ホイールジョイント
	const P2WheelJoint wheelJoint = world.createWheelJoint(car, wheel, wheel.getPos(), Vec2{ 0, 1 })
		.setLimitsEnabled(true);

	const P2Body box = world.createPolygon(P2Dynamic, Vec2{ 0, 0 }, LineString{ Vec2{-100, 0}, Vec2{-100, 100}, Vec2{100, 100}, {Vec2{100, 0}} }.calculateBuffer(5), P2Material{ .friction = 0.0 });
	
	// 距離ジョイント
	const P2DistanceJoint distanceJointL = world.createDistanceJoint(car, car.getPos(), box, Vec2{-100, 0}, 400);
	const P2DistanceJoint distanceJointR = world.createDistanceJoint(car, car.getPos(), box, Vec2{ 100, 0}, 400);

	Array<P2Body> balls;

	// マウスジョイント
	P2MouseJoint mouseJoint;

	// 2D カメラ
	Camera2D camera{ Vec2{ 0, -150 } };

	while (System::Update())
	{
		for (accumulatorSec += Scene::DeltaTime(); stepSec <= accumulatorSec; accumulatorSec -= stepSec)
		{
			world.update(stepSec);
		}

		// こぼれたボールの削除
		balls.remove_if([](const P2Body& b) { return (600 < b.getPos().y); });

		// 2D カメラの更新
		camera.update();
		{
			// 2D カメラから Transformer2D を作成
			const auto t = camera.createTransformer();

			// マウスジョイントによる干渉
			if (MouseL.down())
			{
				mouseJoint = world.createMouseJoint(box, Cursor::PosF())
					.setMaxForce(box.getMass() * 5000.0)
					.setLinearStiffness(2.0, 0.7);
			}
			else if (MouseL.pressed())
			{
				mouseJoint.setTargetPos(Cursor::PosF());
			}
			else if (MouseL.up())
			{
				mouseJoint.release();
			}

			if (KeySpace.pressed())
			{
				// ボールの追加
				balls << world.createCircle(P2Dynamic, Cursor::PosF(), Random(2.0, 4.0), P2Material{ .density = 0.001, .restitution = 0.5, .friction = 0.0 });
			}

			rail.draw(Palette::Gray);
			wheel.draw(Palette::Gray).drawWireframe(1, Palette::Yellow);
			car.draw(ColorF{ 0.3, 0.8, 0.5 });
			box.draw(ColorF{ 0.3, 0.8, 0.5 });

			for (const auto& ball : balls)
			{
				ball.draw(Palette::Skyblue);
			}

			distanceJointL.draw();
			distanceJointR.draw();

			mouseJoint.draw();
		}

		// 2D カメラの操作を描画
		camera.draw(Palette::Orange);
	}
}

絵文字タワー (v0.6 版)

# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	// ウィンドウを 1280x720 にリサイズ
	Window::Resize(1280, 720);

	// 背景色を設定
	Scene::SetBackground(ColorF{ 0.2, 0.6, 1.0 });

	// 登場する絵文字
	const Array<String> emojis = { U"🐘", U"🐧", U"🐐", U"🐤" };
	Array<MultiPolygon> polygons;
	Array<Texture> textures;
	for (const auto& emoji : emojis)
	{
		// 絵文字の画像から形状情報を作成
		polygons << Emoji::CreateImage(emoji).alphaToPolygonsCentered().simplified(2.0);

		// 絵文字の画像からテクスチャを作成
		textures << Texture{ Emoji{ emoji } };
	}

	// 2D 物理演算のシミュレーションステップ(秒)
	constexpr double stepSec = (1.0 / 200.0);

	// 2D 物理演算のシミュレーション蓄積時間(秒)
	double accumulatorSec = 0.0;

	// 2D 物理演算のワールド
	P2World world;

	// [_] 地面
	const P2Body ground = world.createLine(P2Static, Vec2{ 0, 0 }, Line{ -300, 0, 300, 0 });
	
	// 動物の物体
	Array<P2Body> bodies;

	// 物体の ID と絵文字のインデックスの対応テーブル
	HashTable<P2BodyID, size_t> table;

	// 絵文字のインデックス
	size_t index = Random(polygons.size() - 1);

	// 2D カメラ
	Camera2D camera{ Vec2{ 0, -200 } };

	while (System::Update())
	{
		for (accumulatorSec += Scene::DeltaTime(); stepSec <= accumulatorSec; accumulatorSec -= stepSec)
		{
			// 2D 物理演算のワールドを更新
			world.update(stepSec);
		}

		// 地面より下に落ちた物体は削除
		for (auto it = bodies.begin(); it != bodies.end();)
		{
			if (100 < it->getPos().y)
			{
				// 対応テーブルからも削除
				table.erase(it->id());

				it = bodies.erase(it);
			}
			else
			{
				++it;
			}
		}

		// 2D カメラの更新
		camera.update();
		{
			// 2D カメラから Transformer2D を作成
			const auto t = camera.createTransformer();

			// 左クリックされたら
			if (MouseL.down())
			{
				// ボディを追加
				bodies << world.createPolygons(P2Dynamic, Cursor::PosF(), polygons[index], P2Material{ 0.1, 0.0, 1.0 });

				// ボディ ID と絵文字のインデックスの組を対応テーブルに追加
				table.emplace(bodies.back().id(), std::exchange(index, Random(polygons.size() - 1)));
			}

			// すべてのボディを描画
			for (const auto& body : bodies)
			{
				textures[table[body.id()]].rotated(body.getAngle()).drawAt(body.getPos());
			}

			// 地面を描画
			ground.draw(Palette::Green);

			// 現在操作できる絵文字を描画
			textures[index].drawAt(Cursor::PosF(), AlphaF(0.5 + Periodic::Sine0_1(1s) * 0.5));
		}

		// 2D カメラの操作を描画
		camera.draw(Palette::Orange);
	}
}

Sketch to P2Body (v0.6 版)

# include <Siv3D.hpp> // OpenSiv3D v0.6

void Main()
{
	// ウィンドウを 1280x720 にリサイズ
	Window::Resize(1280, 720);

	// 2D 物理演算のシミュレーションステップ(秒)
	constexpr double stepSec = (1.0 / 200.0);

	// 2D 物理演算のシミュレーション蓄積時間(秒)
	double accumulatorSec = 0.0;

	// 2D 物理演算のワールド
	P2World world;

	// [_] 地面
	const P2Body ground = world.createLine(P2Static, Vec2{ 0, 0 }, Line{ -600, 0, 600, 0 });
	
	// 物体
	Array<P2Body> bodies;

	// 2D カメラ
	Camera2D camera{ Vec2{ 0, -300 } };

	LineString points;

	while (System::Update())
	{
		for (accumulatorSec += Scene::DeltaTime(); stepSec <= accumulatorSec; accumulatorSec -= stepSec)
		{
			// 2D 物理演算のワールドを更新
			world.update(stepSec);
		}

		// 地面より下に落ちた物体は削除
		bodies.remove_if([](const P2Body& b) { return (100 < b.getPos().y); });

		// 2D カメラの更新
		camera.update();
		{
			// 2D カメラから Transformer2D を作成
			const auto t = camera.createTransformer();

			// 左クリックされたら
			if (MouseL.down())
			{
				points << Cursor::PosF();
			}
			else if (MouseL.pressed() && !Cursor::DeltaF().isZero())
			{
				points << Cursor::PosF();
			}
			else if (MouseL.up())
			{
				points = points.simplified(2.0);

				if (const Polygon polygon = Polygon::CorrectOne(points))
				{
					const Vec2 pos = polygon.centroid();

					bodies << world.createPolygon(P2Dynamic, pos, polygon.movedBy(-pos));
				}

				points.clear();
			}

			// すべてのボディを描画
			for (const auto& body : bodies)
			{
				body.draw(HSV{ body.id() * 10.0 });
			}

			// 地面を描画
			ground.draw(Palette::Skyblue);

			points.draw(3);
		}

		// 2D カメラの操作を描画
		camera.draw(Palette::Orange);
	}
}

子プロセス作成 (v0.6 版)

# include <Siv3D.hpp>

void Main()
{
# if SIV3D_PLATFORM(WINDOWS)

    // 子プロセスを作成
    ChildProcess child{ U"C:/Windows/System32/notepad.exe" };

# elif SIV3D_PLATFORM(MACOS)

    // 子プロセスを作成
    ChildProcess child{ U"/System/Applications/Calculator.app/Contents/MacOS/Calculator" };

# elif SIV3D_PLATFORM(LINUX)

    // 子プロセスを作成
    ChildProcess child{ U"/usr/bin/firefox", U"www.mozilla.org" };

# endif

    if (!child)
    {
        throw Error{ U"Failed to create a process" };
    }

    while (System::Update())
    {
        ClearPrint();

        // プロセスが実行中かを取得
        Print << child.isRunning();

        // プロセスが終了した場合、その終了コード
        Print << child.getExitCode();

        if (child.isRunning())
        {
            if (SimpleGUI::Button(U"Terminate", Vec2{ 600, 20 }))
            {
                // プロセスを強制終了
                child.terminate();
            }
        }
    }
}

プロセスとの標準入出力のパイプライン処理 (v0.6 版)

# include <Siv3D.hpp>

void Main()
{
# if SIV3D_PLATFORM(WINDOWS)

    // 子プロセスを作成(パイプライン処理)
    ChildProcess child{ U"Console.exe", Pipe::StdInOut };

# else

    // 子プロセスを作成(パイプライン処理)
    ChildProcess child{ U"Console", Pipe::StdInOut };

# endif

    if (!child)
    {
        throw Error{ U"Failed to create a process" };
    }

    child.ostream() << 10 << std::endl;
    child.ostream() << 20 << std::endl;

    int32 result;
    child.istream() >> result;
    Print << U"result: " << result;

    while (System::Update())
    {

    }
}

子プロセスのプログラム

# include <iostream>

int main()
{
    int a, b;
    std::cin >> a >> b;
    std::cout << (a + b) << std::endl;
}

XInput (v0.6 版)

https://twitter.com/Reputeless/status/1380177849919791106

# include <Siv3D.hpp> // OpenSiv3D v0.6

Polygon MakeGamePadPolygon()
{
	Polygon polygon = Ellipse{ 400, 480, 300, 440 }.asPolygon(64);
	polygon = Geometry2D::Subtract(polygon, Ellipse{ 400, 40, 220, 120 }.asPolygon(48)).front();
	polygon = Geometry2D::Subtract(polygon, Circle{ 400, 660, 240 }.asPolygon(48)).front();
	polygon = Geometry2D::Subtract(polygon, Rect{ 0, 540, 800, 60 }.asPolygon()).front();
	return polygon;
}

void Main()
{
	constexpr ColorF backgroundColor{ 0.6, 0.8, 0.7 };
	Scene::SetBackground(backgroundColor);

	constexpr Ellipse buttonLB{ 210, 150, 50, 24 };
	constexpr Ellipse buttonRB{ 590, 150, 50, 24 };
	const Polygon gamepadPolygon = MakeGamePadPolygon();
	constexpr Circle logo{ 400, 250, 25 };
	constexpr RectF leftTrigger{ 210, 16, 40, 100 };
	constexpr RectF rightTrigger{ 550, 16, 40, 100 };
	constexpr Circle leftThumb{ 230, 250, 35 };
	constexpr Circle rightThumb{ 480, 350, 35 };
	constexpr Circle dPad{ 320, 350, 40 };
	constexpr Circle buttonA{ 570, 300, 20 };
	constexpr Circle buttonB{ 620, 250, 20 };
	constexpr Circle buttonX{ 520, 250, 20 };
	constexpr Circle buttonY{ 570, 200, 20 };
	constexpr Circle buttonView{ 330, 250, 15 };
	constexpr Circle buttonMenu{ 470, 250, 15 };

	// プレイヤーインデックス (0 - 7)
	size_t playerIndex = 0;
	const Array<String> options = Range(1, 8).map([](int32 i) {return U"{}P"_fmt(i); });

	// デッドゾーンを有効にするか
	bool enableDeadZone = false;

	// 振動 (0.0 - 1.0)
	XInputVibration vibration;

	while (System::Update())
	{
		// 指定したプレイヤーインデックスの XInput コントローラを取得
		auto controller = XInput(playerIndex);

		// デッドゾーン
		if (enableDeadZone)
		{
			// それぞれデフォルト値を設定
			controller.setLeftTriggerDeadZone();
			controller.setRightTriggerDeadZone();
			controller.setLeftThumbDeadZone();
			controller.setRightThumbDeadZone();
		}
		else
		{
			// デッドゾーンを無効化
			controller.setLeftTriggerDeadZone(DeadZone{});
			controller.setRightTriggerDeadZone(DeadZone{});
			controller.setLeftThumbDeadZone(DeadZone{});
			controller.setRightThumbDeadZone(DeadZone{});
		}

		// 振動
		controller.setVibration(vibration);

		// L ボタン、R ボタン
		{
			buttonLB.draw(ColorF{ controller.buttonLB.pressed() ? 1.0 : 0.7 });
			buttonRB.draw(ColorF{ controller.buttonRB.pressed() ? 1.0 : 0.7 });
		}

		// 本体
		gamepadPolygon.draw(ColorF{ 0.9 });

		// Xbox ボタン
		{
			if (controller.isConnected())
			{
				Circle{ logo.center, 32 }
				.drawPie((-0.5_pi + 0.5_pi * controller.playerIndex), 0.5_pi, ColorF{ 0.6, 0.9, 0.3 });
			}

			logo.draw(ColorF{ 0.6 });
		}

		// 左トリガー
		{
			leftTrigger.draw(AlphaF(0.25));
			leftTrigger.stretched((controller.leftTrigger - 1.0) * leftTrigger.h, 0, 0, 0).draw();
		}

		// 右トリガー
		{
			rightTrigger.draw(AlphaF(0.25));
			rightTrigger.stretched((controller.rightTrigger - 1.0) * rightTrigger.h, 0, 0, 0).draw();
		}

		// 左スティック
		{
			leftThumb.draw(ColorF{ controller.buttonLThumb.pressed() ? 0.85 : 0.5 });
			Circle{ leftThumb.center + 25 * Vec2{ controller.leftThumbX, -controller.leftThumbY }, 20 }.draw();
		}

		// 右スティック
		{
			rightThumb.draw(ColorF{ controller.buttonRThumb.pressed() ? 0.85 : 0.5 });
			Circle{ rightThumb.center + 25 * Vec2{ controller.rightThumbX, -controller.rightThumbY }, 20 }.draw();
		}

		// 方向パッド
		{
			dPad.draw(ColorF{ 0.75 });
			Shape2D::Plus(dPad.r * 0.9, 25, dPad.center).draw(ColorF{ 0.5 });

			const Vec2 direction{
				controller.buttonRight.pressed() - controller.buttonLeft.pressed(),
				controller.buttonDown.pressed() - controller.buttonUp.pressed() };

			if (!direction.isZero())
			{
				Circle{ dPad.center + direction.setLength(25), 15 }.draw();
			}
		}

		// A, B, X, Y ボタン
		{
			buttonA.draw(ColorF{ 0.0, 1.0, 0.3, controller.buttonA.pressed() ? 1.0 : 0.3 });
			buttonB.draw(ColorF{ 1.0, 0.0, 0.3, controller.buttonB.pressed() ? 1.0 : 0.3 });
			buttonX.draw(ColorF{ 0.0, 0.3, 1.0, controller.buttonX.pressed() ? 1.0 : 0.3 });
			buttonY.draw(ColorF{ 1.0, 0.5, 0.0, controller.buttonY.pressed() ? 1.0 : 0.3 });
		}

		// View (Back), Menu (Start) ボタン 
		{
			buttonView.draw(ColorF(controller.buttonView.pressed() ? 1.0 : 0.7));
			buttonMenu.draw(ColorF(controller.buttonMenu.pressed() ? 1.0 : 0.7));
		}

		SimpleGUI::RadioButtons(playerIndex, options, Vec2{ 20, 20 });
		SimpleGUI::CheckBox(enableDeadZone, U"DeadZone", Vec2{ 320, 20 });
		SimpleGUI::Slider(U"leftMotor", vibration.leftMotor, Vec2{ 280, 420 }, 120, 120);
		SimpleGUI::Slider(U"rightMotor", vibration.rightMotor, Vec2{ 280, 460 }, 120, 120);
		SimpleGUI::Slider(U"leftTrigger", vibration.leftTrigger, Vec2{ 280, 500 }, 120, 120);
		SimpleGUI::Slider(U"rightTrigger", vibration.rightTrigger, Vec2{ 280, 540 }, 120, 120);
	}
}
作成者以外のコメントは許可されていません