図形のサンプル¶
1. 市松模様の背景¶
コード
# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF{ 0.8 });
constexpr int32 CellSize = 20;
while (System::Update())
{
for (int32 y = 0; y < (Scene::Height() / CellSize); ++y)
{
for (int32 x = 0; x < (Scene::Width() / CellSize); ++x)
{
if (IsEven(y + x))
{
Rect{ (x * CellSize), (y * CellSize), CellSize }.draw(ColorF{ 0.75 });
}
}
}
}
}
2. 不規則に見える長方形グリッド¶
コード
# include <Siv3D.hpp>
//
// 参考: OffGrid by Chris Cox
// https://gitlab.com/chriscox/offgrid
//
uint64 g_seed = RandomUint64();
Vec2 PointToRandomVector(int32 x, int32 y)
{
PRNG::SplitMix64 rng{ (Point{ x, y }.hash() ^ g_seed) };
return RandomVec2(Circle{ 1 }, rng);
}
Vec2 GetXY(int32 x, int32 y, double t, const Vec2& offset, const double cellSize)
{
const Vec2 pos{ offset + (cellSize * Vec2{ x, y }) };
return (pos + (PointToRandomVector(x, y) * t * (cellSize * 0.5)));
}
ColorF GetColor(int32 x, int32 y)
{
PRNG::SplitMix64 rng{ (Point{ x, y }.hash() ^ g_seed) };
return HSV{ Random(60.0, 140.0, rng), Random(0.5, 0.9, rng), Random(0.4, 1.0, rng) };
}
void Main()
{
Window::Resize(1280, 720);
Scene::SetBackground(ColorF{ 1.0 });
constexpr Size CellCount{ 10, 6 };
constexpr Vec2 Offset{ 60, 80 };
constexpr double CellSize = 90.0;
constexpr ColorF LineColor{ 0.15 };
constexpr double LineThickness = 4.0;
constexpr double LineLengthHalf = (CellSize * 0.4);
double t = 0.0;
bool showLine = true;
while (System::Update())
{
SimpleGUI::Slider(t, Vec2{ 1030, 60 }, 160);
SimpleGUI::CheckBox(showLine, U"Show line", Vec2{ 1030, 100 });
if (SimpleGUI::Button(U"New seed", Vec2{ 1030, 140 }))
{
g_seed = RandomUint64();
}
for (int32 y = 0; y < CellCount.y; ++y)
{
for (int32 x = 0; x < CellCount.x; ++x)
{
const Vec2 p0 = GetXY(x, y, t, Offset, CellSize);
const Vec2 p1 = GetXY(x + 1, y, t, Offset, CellSize);
const Vec2 p2 = GetXY(x, y + 1, t, Offset, CellSize);
const Vec2 p3 = GetXY(x + 1, y + 1, t, Offset, CellSize);
const ColorF color = GetColor(x, y);
if (IsEven(x + y))
{
const double top = p0.y;
const double bottom = p3.y;
const double left = p2.x;
const double right = p1.x;
RectF{ left, top, (right - left), (bottom - top) }.stretched(-1).draw(color);
}
else
{
const double top = p1.y;
const double bottom = p2.y;
const double left = p0.x;
const double right = p3.x;
RectF{ left, top, (right - left), (bottom - top) }.stretched(-1).draw(color);
}
}
}
if (showLine)
{
for (int32 y = 0; y <= CellCount.y; ++y)
{
for (int32 x = 0; x <= CellCount.x; ++x)
{
const Vec2 p0 = GetXY(x, y, t, Offset, CellSize);
if (IsEven(x + y))
{
Line{ p0.movedBy(-LineLengthHalf, 0), p0.movedBy(LineLengthHalf, 0) }
.draw(LineStyle::RoundCap, LineThickness, LineColor);
}
else
{
Line{ p0.movedBy(0, -LineLengthHalf), p0.movedBy(0, LineLengthHalf) }
.draw(LineStyle::RoundCap, LineThickness, LineColor);
}
}
}
}
}
}
3. ボロノイ図¶
コード
# include <Siv3D.hpp>
void Main()
{
constexpr Size SceneSize{ 1280, 720 };
constexpr Rect SceneRect{ SceneSize };
Window::Resize(SceneSize);
Subdivision2D subdiv{ SceneRect };
// シーンの長方形内にほどよい間隔で点を生成する
for (const PoissonDisk2D pd{ SceneSize, 40 };
const auto& point : pd.getPoints())
{
if (SceneRect.contains(point))
{
subdiv.addPoint(point);
}
}
const Array<Polygon> facetPolygons = subdiv
.calculateVoronoiFacets() // ボロノイ図を計算する
.map([SceneRect](const VoronoiFacet& f) // シーンの長方形内にクリッピングする
{
return Geometry2D::And(Polygon{ f.points }, SceneRect).front();
});
while (System::Update())
{
for (auto&& [i, facetPolygon] : Indexed(facetPolygons))
{
facetPolygon
.draw(HSV{ (i * 25.0), 0.5, 0.9 })
.drawFrame(3, ColorF{ 1.0 });
}
}
}
4. ボロノイ図・ドロネー図の動的な生成¶
コード
# include <Siv3D.hpp>
void Main()
{
constexpr Size SceneSize{ 1280, 720 };
constexpr Rect SceneRect{ SceneSize };
constexpr Rect AreaRect = SceneRect.stretched(-50);
Window::Resize(SceneSize);
Scene::SetBackground(ColorF{ 0.99 });
Subdivision2D subdiv{ AreaRect };
// ドロネー三角形分割の三角形リスト
Array<Triangle> triangles;
// ボロノイ図の情報のリスト
Array<VoronoiFacet> facets;
// facets を長方形でクリップし Polygon に変換したリスト
Array<Polygon> facetPolygons;
while (System::Update())
{
const Vec2 pos = Cursor::PosF();
// 長方形上をクリックしたら
if (AreaRect.leftClicked())
{
// 点を追加
subdiv.addPoint(pos);
// ドロネー三角形分割の計算
subdiv.calculateTriangles(triangles);
// ボロノイ図を計算する
subdiv.calculateVoronoiFacets(facets);
// エリアの範囲内にクリッピングする
facetPolygons = facets.map([AreaRect](const VoronoiFacet& f)
{
return Geometry2D::And(Polygon{ f.points }, AreaRect).front();
});
}
AreaRect.draw(ColorF{ 0.75 });
for (auto&& [i, facetPolygon] : Indexed(facetPolygons))
{
facetPolygon.draw(HSV{ (i * 25.0), 0.65, 0.8 }).drawFrame(3, ColorF{ 0.25 });
}
for (const auto& triangle : triangles)
{
triangle.drawFrame(2.5, ColorF{ 0.9 });
}
for (const auto& facet : facets)
{
Circle{ facet.center, 6 }.drawFrame(5).draw(ColorF{ 0.25 });
}
// 現在のマウスカーソルから最短距離にある点を探す
if (const auto nearestVertexID = subdiv.findNearest(pos))
{
const Vec2 nearestVertex = subdiv.getVertex(nearestVertexID.value());
Line{ pos, nearestVertex }.draw(LineStyle::RoundDot, 6, ColorF{ 0.75 });
Circle{ nearestVertex, 16 }.drawFrame(3.5);
}
}
}
5. 図形の輪郭の一部を LineString として取得する¶
コード
# include <Siv3D.hpp>
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 });
}
}
6. GPU での頂点生成¶
コード
# include <Siv3D.hpp>
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 } }};
if (not vs)
{
throw Error{ U"Failed to load a shader file" };
}
ConstantBuffer<float> cb;
while (System::Update())
{
cb = static_cast<float>(Scene::Time());
Graphics2D::SetVSConstantBuffer(1, cb);
{
const ScopedCustomShader2D shader{ vs };
// 頂点情報の無い三角形を 360 個描画する
// (頂点情報は頂点シェーダで設定する)
Graphics2D::DrawTriangles(360);
}
}
}
7. 長方形詰込み¶
コード
# include <Siv3D.hpp>
// 画面上に散らばるランダムな長方形の配列を作成する関数
Array<Rect> GenerateRandomRects()
{
Array<Rect> rects(Random(4, 32));
for (auto& rect : rects)
{
const Point center = RandomPoint(Scene::Rect().stretched(-80));
rect = Rect{ Arg::center = center, 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 stopwatch;
while (System::Update())
{
if ((not stopwatch.isStarted()) || (1.8s < stopwatch))
{
input = GenerateRandomRects();
rotations.resize(input.size());
rotations.fill(0.0);
// AllowFlip::Yes を指定すると、90° 回転による詰め込みを許可する
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);
}
stopwatch.restart();
}
// アニメーション
const double k = Min(stopwatch.sF() * 10, 1.0);
const double t = Math::Saturate(stopwatch.sF() - 0.2);
const double e = EaseInOutExpo(t);
Rect{ offset, output.size }.draw(ColorF{ 0.7, e });
for (size_t i = 0; i < input.size(); ++i)
{
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 });
}
}
}
8. 六角形タイル¶
コード
# include <Siv3D.hpp>
namespace Hex
{
inline constexpr Vec2 IndexToPixel(const Point& index, const double hexR) noexcept
{
const double tileWidth = (hexR * Math::Sqrt3);
const double halfWidth = (tileWidth * 0.5);
const double tileHeight = (hexR * 1.5);
return{ (index.x * tileWidth + IsOdd(index.y) * halfWidth), (index.y * tileHeight) };
}
// 参考
// https://stackoverflow.com/questions/7705228/hexagonal-grids-how-do-you-find-which-hexagon-a-point-is-in
inline Point PixelToIndex(const Vec2& _pos, const double hexR)
{
const double tileWidth = (hexR * Math::Sqrt3);
const double halfWidth = (tileWidth * 0.5);
const double tileHeight = (hexR * 1.5);
const Vec2 pos{ (_pos.x + halfWidth), (_pos.y + hexR) };
int32 row = static_cast<int32>(Math::Floor(pos.y / tileHeight));
const bool rowIsOdd = IsOdd(row);
int32 column = static_cast<int32>(Math::Floor(rowIsOdd ? ((pos.x - halfWidth) / tileWidth) : (pos.x / tileWidth)));
const double relY = (pos.y - (row * tileHeight));
const double relX = (rowIsOdd ? ((pos.x - (column * tileWidth)) - halfWidth) : (pos.x - (column * tileWidth)));
const double c = (hexR * 0.5);
const double m = (c / halfWidth);
if (relY < (-m * relX) + c)
{
return{ (column - (not rowIsOdd)), (row - 1) };
}
else if (relY < (m * relX) - c)
{
return{ (column + rowIsOdd), (row - 1) };
}
return{ column, row };
}
}
void Main()
{
Scene::SetBackground(ColorF{ 0.5, 0.6, 0.7 });
const Font font{ FontMethod::MSDF, 48, Typeface::Bold };
constexpr Vec2 Offset{ 60, 60 };
constexpr double HexRadius = 50.0;
const Size GridSize{ 8, 7 };
while (System::Update())
{
for (auto p : step(GridSize))
{
const Vec2 center = (Hex::IndexToPixel(p, HexRadius) + Offset);
Shape2D::Hexagon(HexRadius, center)
.draw(ColorF{ 0.75 })
.drawFrame(2);
font(p).drawAt(16, center);
}
{
const Point index = Hex::PixelToIndex(Cursor::Pos() - Offset, HexRadius);
const Vec2 center = (Hex::IndexToPixel(index, HexRadius) + Offset);
Shape2D::Hexagon(HexRadius, center).drawFrame(10);
}
}
}
9. 2D マップの可視領域¶
コード
# include <Siv3D.hpp>
class VisibilityMap
{
public:
explicit VisibilityMap(const RectF& region)
: m_region{ region }
, m_maxDistance{ m_region.w + m_region.h }
{
add(m_region);
}
void add(const Triangle& t)
{
m_lines << t.side(0) << t.side(1) << t.side(2);
}
void add(const RectF& r)
{
m_lines << r.top() << r.right() << r.bottom() << r.left();
}
void add(const Quad& q)
{
m_lines << q.side(0) << q.side(1) << q.side(2) << q.side(3);
}
void add(const Circle& c, int32 quality = 8)
{
const double da = (2_pi / Max(quality, 6));
for (int32 i = 0; i < quality; ++i)
{
m_lines.emplace_back(c.getPointByAngle(da * i), c.getPointByAngle(da * (i + 1)));
}
}
void add(const Polygon& p)
{
const auto& outer = p.outer();
for (size_t i = 0; i < outer.size(); ++i)
{
m_lines.emplace_back(outer[i], outer[(i + 1) % outer.size()]);
}
}
template <class Shape>
void add(const Array<Shape>& shapes)
{
for (const auto& shape : shapes)
{
add(shape);
}
}
const RectF& getRegion() const
{
return m_region;
}
Array<Triangle> calculateVisibilityTriangles(const Vec2& eyePos) const
{
const auto points = calculateCollidePoints(eyePos);
Array<Triangle> triangles(points.size());
for (size_t i = 0; i < triangles.size(); ++i)
{
triangles[i].set(eyePos, points[i].second, points[(i + 1) % points.size()].first);
}
return triangles;
}
private:
static constexpr double m_epsilon = 1e-10;
RectF m_region;
double m_maxDistance = 0.0;
Array<Line> m_lines;
const Array<std::pair<Vec2, Vec2>> calculateCollidePoints(const Vec2& eyePos) const
{
if (not m_region.stretched(-1).contains(eyePos))
{
return{};
}
Array<double> angles{ Arg::reserve = m_lines.size() };
{
for (const auto& line : m_lines)
{
const Vec2 v = (line.begin - eyePos);
angles.push_back(Math::Atan2(v.y, v.x));
}
angles.sort();
}
Array<std::pair<Vec2, Vec2>> points{ Arg::reserve = angles.size() };
for (auto angle : angles)
{
const double left = (angle - m_epsilon);
const double right = (angle + m_epsilon);
const Line leftRay{ eyePos, Arg::direction = (Vec2::Right().rotated(left) * m_maxDistance) };
const Line rightRay{ eyePos, Arg::direction = (Vec2::Right().rotated(right) * m_maxDistance) };
Vec2 leftCollidePoint = leftRay.end;
Vec2 rightCollidePoint = rightRay.end;
for (const auto& line : m_lines)
{
if (const auto p = leftRay.intersectsAt(line))
{
if (p->distanceFromSq(eyePos) < leftCollidePoint.distanceFromSq(eyePos))
{
leftCollidePoint = *p;
}
}
if (const auto p = rightRay.intersectsAt(line))
{
if (p->distanceFromSq(eyePos) < rightCollidePoint.distanceFromSq(eyePos))
{
rightCollidePoint = *p;
}
}
}
points.emplace_back(leftCollidePoint, rightCollidePoint);
}
return points;
}
};
void Main()
{
Window::Resize(1280, 720);
constexpr ColorF objectColor = Palette::Deepskyblue;
const Array<Triangle> triangles{ Triangle{ 120, 120, 300, 120, 120, 500 } };
const Array<RectF> rects{ Rect{ 600, 40, 40, 260 }, Rect{ 440, 300, 440, 40 }, Rect{ 1040, 300, 200, 40 }, Rect{ 480, 480, 240, 100 } };
const Array<Circle> circles{ Circle{ 1000, 500, 80 }, Circle{ 460, 180, 30 }, Circle{ 240, 480, 30 }, Circle{ 300, 560, 30 } };
const Array<Polygon> polygons{ Shape2D::Star(60, Vec2{ 940, 180 }) };
VisibilityMap map(Rect{ 40, 40, 1200, 640 });
{
map.add(triangles);
map.add(rects);
map.add(circles);
map.add(polygons);
}
while (System::Update())
{
Cursor::RequestStyle(CursorStyle::Hidden);
for (const auto& triangle : triangles)
{
triangle.draw(objectColor);
}
for (const auto& rect : rects)
{
rect.draw(objectColor);
}
for (const auto& circle : circles)
{
circle.draw(objectColor);
}
for (const auto& polygon : polygons)
{
polygon.draw(objectColor);
}
map.getRegion().drawFrame(0, 8, objectColor);
const Vec2 eyePos = Cursor::Pos();
const auto vTriangles = map.calculateVisibilityTriangles(eyePos);
for (const auto& vTriangle : vTriangles)
{
vTriangle.draw(ColorF{ 1.0, 0.5 });
}
Circle{ eyePos, 20 }.draw(Palette::Orange).drawFrame(1, 2);
}
}
10. ほどよい距離で重ならない点群を生成する¶
コード
# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
constexpr Rect AreaRect{ 100, 120, 600, 400 };
double r = 15.0;
// 点群を生成する
PoissonDisk2D pd{ AreaRect.size, r };
while (System::Update())
{
AreaRect.stretched(r).draw(ColorF{ 0.7 });
AreaRect.draw(ColorF{ 0.2 });
for (const auto& point : pd.getPoints())
{
Circle{ point, (r / 4) }.movedBy(AreaRect.pos).draw();
}
if (SimpleGUI::Slider(r, 5.0, 40.0, Vec2{ 40, 40 }))
{
// 点群を再生成する
pd = PoissonDisk2D{ AreaRect.size, r };
}
}
}
11. Sketch to Polygon¶
コード
# include <Siv3D.hpp>
void Main()
{
// 作成した Polygon の配列
Array<Polygon> polygons;
// 書き途中の LineString
LineString points;
while (System::Update())
{
// 左クリックもしくはクリックしたままの移動が発生したら
if (MouseL.down() ||
(MouseL.pressed() && (not Cursor::DeltaF().isZero())))
{
points << Cursor::PosF();
}
else if (MouseL.up())
{
points = points.simplified(2.0);
if (const Polygon polygon = Polygon::CorrectOne(points))
{
polygons << polygon;
}
points.clear();
}
// それぞれの Polygon を描画する
for (auto&& [i, polygon] : Indexed(polygons))
{
polygon.draw(HSV{ (i * 20), 0.4, 1.0 })
.drawWireframe(1, Palette::Gray)
.drawFrame(4, HSV{ i * 20 });
}
points.draw(4);
}
}