Open5
Siv3D v0.6.12 新規追加・更新サンプル
Rect / RectF から角度を指定して平行四辺形を作成
# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
while (System::Update())
{
const double angle = (Periodic::Sine1_1(6s) * 45_deg);
for (int32 i = 0; i < 6; ++i)
{
Rect{ (50 + i * 120), (100 + i * 40), (20 + i * 20), (100 + i * 40)}
.draw()
.skewedX(angle)
.draw(HSV{ (i * 60), 0.6, 0.9 });
}
}
}
Morton Order
2D
# include <Siv3D.hpp>
void DFS(const Morton32 morton, const uint32 currentLevel, const uint32 maxLevel)
{
// 最大レベルにおける 1 辺のセルの数
const int32 baseGridCount = (1 << maxLevel);
// 現在のレベルにおける 1 辺のセルの数
const uint32 n = (1 << currentLevel);
// 現在のレベルにおけるセルのサイズ
const int32 currentCellSize = ((baseGridCount / n) * (720 / baseGridCount));
// モートン順序から二次元のインデックスを取得
const Point index = Morton::Decode2D32(morton);
// 現在のセルの正方形
const Rect rect{ (index * currentCellSize), currentCellSize };
if (not rect.mouseOver())
{
return;
}
rect.draw(Colormap01F(currentLevel / (maxLevel + 1.0)));
// 最大レベルに達したら終了
if (currentLevel == maxLevel)
{
return;
}
// 子のレベル
const uint32 childLevel = (currentLevel + 1);
// 子のモートン順序
const Morton32 childMortonBase = (morton << 2);
for (uint32 i = 0; i < 4; ++i)
{
DFS((childMortonBase + i), childLevel, maxLevel);
}
}
void Main()
{
Window::Resize(1280, 720);
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
const Font font{ FontMethod::MSDF, 48, Typeface::Bold };
size_t maxLevel = 3;
LineString points;
while (System::Update())
{
const int32 baseCellSize = (720 / (1 << maxLevel));
const double circleRadius = (baseCellSize / 2.8);
const double fontSize = (baseCellSize / 3.0);
const double lineThickness = (baseCellSize / 12.0);
if (not points)
{
for (Morton32 morton = 0; morton < (1u << (maxLevel * 2)); ++morton)
{
const Point index = Morton::Decode2D32(morton);
points << ((index * baseCellSize) + (Point{ baseCellSize, baseCellSize } / 2));
}
}
DFS(0, 0, static_cast<uint32>(maxLevel));
points.draw(lineThickness);
for (const auto& point : points)
{
point.asCircle(circleRadius).draw();
}
{
Morton32 morton = 0;
for (const auto& point : points)
{
font(morton++).drawAt(fontSize, point, ColorF{ 0.11 });
}
}
if (SimpleGUI::RadioButtons(maxLevel, { U"0", U"1", U"2", U"3", U"4", U"5" }, Vec2{ 760, 40 }))
{
points.clear();
}
}
}
3D
# include <Siv3D.hpp>
void Main()
{
Window::Resize(1280, 720);
const ColorF backgroundColor = ColorF{ 0.4, 0.6, 0.8 }.removeSRGBCurve();
const MSRenderTexture renderTexture{ Scene::Size(), TextureFormat::R8G8B8A8_Unorm_SRGB, HasDepth::Yes };
BasicCamera3D camera{ renderTexture.size(), 30_deg, Vec3{ 10, 16, -32 } };
Stopwatch stopwatch{ StartImmediately::Yes };
Timer timer{ 16s };
while (System::Update())
{
ClearPrint();
if (KeySpace.down())
{
stopwatch.restart();
timer.reset();
}
const Vec3 eyePos = Cylindrical{ 18, -160_deg + stopwatch.sF() * 3_deg, 11 } + Vec3{ 4, 0, -6 };
const Vec3 focusPos{ 4, 4.5, -6 };
camera.setView(eyePos, focusPos);
Graphics3D::SetCameraTransform(camera);
{
const ScopedRenderTarget3D target{ renderTexture.clear(backgroundColor) };
Plane{ 64 }.draw();
constexpr uint32 N = 3;
if ((not timer.isStarted()) && 36s < stopwatch)
{
timer.start();
}
if (timer.isStarted())
{
const double t = ((timer.sF() * 2.0) / 0.06);
for (uint32 morton = 0; morton < Min<int64>((1 << (N * 3)), int32(t)); ++morton)
{
const Vec3 center = ((Morton::Decode3D32(morton) + Vec3{ 0.5, 0.5, 0.5 }) * Vec3 { 1, 1, -1 });
const double scale = EaseInQuart(Min((t - morton) * 0.04, 1.0));
Box{ center, scale }.draw(HSV{ morton * 2.0, 0.6, 0.85 }.removeSRGBCurve());
}
}
else
{
const double t = ((stopwatch.sF() - 2.0) / 0.06);
for (uint32 morton = 0; morton < Min<int64>((1 << (N * 3)), int32(t)); ++morton)
{
const Vec3 center = ((Morton::Decode3D32(morton) + Vec3{ 0.5, 0.5, 0.5 }) * Vec3 { 1, 1, -1 });
const double scale = EaseOutExpo(Min((t - morton) * 0.05, 1.0));
Box{ center, scale }.draw(HSV{ morton * 2.0, 0.6, 0.85 }.removeSRGBCurve());
}
}
}
{
Graphics3D::Flush();
renderTexture.resolve();
Shader::LinearToScreen(renderTexture);
}
}
}
Terrain
# include <Siv3D.hpp>
class Terrain
{
public:
static constexpr int32 MaxLevel = 8;
static constexpr int32 GridCount = (1 << (MaxLevel));
static constexpr s3d::Size MapSize{ GridCount, GridCount };
static constexpr double TerrainSize = 256.0;
static constexpr float HeightScale = 16.0f;
Terrain() = default;
const RenderTexture& getHeightmapTexture() const noexcept
{
return m_heightmapTexture;
}
const RenderTexture& getNormalMapTexture() const noexcept
{
return m_normalMapTexture;
}
Vec3 calculateTileCenter(Point index) const noexcept
{
const double x = (index.x - (GridCount / 2) + 0.5) * (TerrainSize / GridCount);
const double y = m_heightmap[index.y][index.x];
const double z = -(index.y - (GridCount / 2) + 0.5) * (TerrainSize / GridCount);
return{ x, y, z };
}
Box calculateBoundingBox(uint32 level, Morton32 morton) const noexcept
{
// 現在のレベルのボックスの数 (1, 4, 16, 64, 256, ... )
const uint32 numIndices = (1 << (2 * level));
const uint32 N = (1 << level);
assert(level <= MaxLevel);
assert(morton < numIndices);
const auto [xi, yi] = Morton::Decode2D32(morton);
const int32 x = (xi * (GridCount / N));
const int32 y = (yi * (GridCount / N));
float minY = Largest<float>;
float maxY = Smallest<float>;
for (uint32 yy = y; yy < (y + (GridCount / N)); ++yy)
{
for (uint32 xx = x; xx < (x + (GridCount / N)); ++xx)
{
const float h = m_heightmap[yy][xx];
minY = Min(minY, h);
maxY = Max(maxY, h);
}
}
const double boxSize = (TerrainSize / N);
const double minX = (-TerrainSize / 2) + (xi * boxSize);
const double maxX = (minX + boxSize);
const double minZ = (-TerrainSize / 2) + (yi * boxSize);
const double maxZ = (minZ + boxSize);
return Box::FromPoints({ minX, 0.0, -minZ }, { maxX, maxY, -maxZ });
}
void clear()
{
m_heightmap.fill(0.0f);
m_heightmapTexture.clear(ColorF{ 0.0 });
m_normalMapTexture.clear(ColorF{ 0.0 });
}
void fillRandomHeightmap()
{
GenerateRandomHeightmap(m_heightmap);
{
const RenderTexture noise{ m_heightmap };
const ScopedRenderTarget2D target{ m_heightmapTexture };
noise.draw();
}
}
void readHeightmapFromHeightmapTexture()
{
m_heightmapTexture.read(m_heightmap);
}
private:
Grid<float> m_heightmap{ MapSize, 0.0f };
RenderTexture m_heightmapTexture{ MapSize, ColorF{ 0.0 }, TextureFormat::R32_Float };
RenderTexture m_normalMapTexture{ MapSize, ColorF{ 0.0 }, TextureFormat::R16G16_Float };
static void GenerateRandomHeightmap(Grid<float>& heightmap)
{
constexpr int32 Octaves = 5;
const PerlinNoiseF noise{ RandomUint64() };
for (int32 y = 0; y < heightmap.height(); ++y)
{
for (int32 x = 0; x < heightmap.width(); ++x)
{
const Float2 pos{ x, y };
const float h = noise.octave2D0_1((pos / 256.0f), Octaves);
heightmap[y][x] = (h * HeightScale);
}
}
}
};
class TerrainRenderer
{
public:
TerrainRenderer() = default;
bool init()
{
m_vsTerrain = HLSL{ U"example/shader/hlsl/terrain_forward.hlsl", U"VS" }
| GLSL{ U"example/shader/glsl/terrain_forward.vert", {{ U"VSPerView", 1 }, { U"VSPerObject", 2 }, { U"VSPerMaterial", 3 }} };
m_psTerrain = HLSL{ U"example/shader/hlsl/terrain_forward.hlsl", U"PS" }
| GLSL{ U"example/shader/glsl/terrain_forward.frag", {{ U"PSPerFrame", 0 }, { U"PSPerView", 1 }, { U"PSPerMaterial", 3 }} };
m_psNormal = HLSL{ U"example/shader/hlsl/terrain_normal.hlsl", U"PS" }
| GLSL{ U"example/shader/glsl/terrain_normal.frag", {{U"PSConstants2D", 0}} };
if ((not m_vsTerrain) || (not m_psTerrain) || (not m_psNormal))
{
return false;
}
m_gridMesh = Mesh{ MeshData::Grid(SizeF{ Terrain::TerrainSize, Terrain::TerrainSize }, 128, 128) };
return true;
}
void draw(const Terrain& terrain, const Texture& grassTexture, const Texture& rockTexture) const
{
const ScopedCustomShader3D shader{ m_vsTerrain, m_psTerrain };
const ScopedRenderStates3D ss{ { ShaderStage::Vertex, 0, SamplerState::ClampLinear} };
Graphics3D::SetVSTexture(0, terrain.getHeightmapTexture());
Graphics3D::SetPSTexture(1, terrain.getNormalMapTexture());
Graphics3D::SetPSTexture(2, rockTexture);
m_gridMesh.draw(grassTexture);
}
void updateNormalMapTexture(const Terrain& terrain)
{
const ScopedRenderTarget2D target{ terrain.getNormalMapTexture() };
const ScopedCustomShader2D shader{ m_psNormal };
const ScopedRenderStates2D blend{ BlendState::Opaque, SamplerState::ClampLinear };
terrain.getHeightmapTexture().draw();
}
private:
VertexShader m_vsTerrain;
PixelShader m_psTerrain;
PixelShader m_psNormal;
Mesh m_gridMesh;
};
void DFS(const Terrain& terrain, const Ray& mouseRay, Morton32 morton, uint32 level, uint32 maxLevel, Array<Morton32>& results)
{
const Box boundingBox = terrain.calculateBoundingBox(level, morton);
if (not boundingBox.intersects(mouseRay))
{
return;
}
if (level == maxLevel)
{
//boundingBox.drawFrame();
results.push_back(morton);
return;
}
const uint32 childLevel = (level + 1);
const Morton32 childMorton = (morton << 2);
for (uint32 i = 0; i < 4; ++i)
{
DFS(terrain, mouseRay, (childMorton + i), childLevel, maxLevel, results);
}
}
void Main()
{
Window::Resize(1280, 720);
const ColorF backgroundColor = ColorF{ 0.4, 0.6, 0.8 }.removeSRGBCurve();
const MSRenderTexture renderTexture{ Scene::Size(), TextureFormat::R8G8B8A8_Unorm_SRGB, HasDepth::Yes };
DebugCamera3D camera{ renderTexture.size(), 30_deg, Vec3{ 10, 16, -32 } };
const Texture grassTexture{ U"example/texture/grass.jpg", TextureDesc::MippedSRGB };
const Texture rockTexture{ U"example/texture/rock.jpg", TextureDesc::MippedSRGB };
const Texture brushTexture{ U"example/particle.png" };
TerrainRenderer terrainRenderer;
if (not terrainRenderer.init())
{
return;
}
Terrain terrain;
while (System::Update())
{
ClearPrint();
camera.update(2.0);
// 3D シーンへの描画
{
Graphics3D::SetCameraTransform(camera);
const ScopedRenderTarget3D target{ renderTexture.clear(backgroundColor) };
terrainRenderer.draw(terrain, grassTexture, rockTexture);
{
const Ray mouseRay = camera.screenToRay(Cursor::PosF());
Array<Morton32> results;
DFS(terrain, mouseRay, 0, 0, Terrain::MaxLevel, results);
Array<std::pair<Morton32, Vec3>> tiles;
for (const Morton32 morton : results)
{
const Point pos = Morton::Decode2D32(morton);
const Vec3 center = terrain.calculateTileCenter(pos);
tiles.emplace_back(morton, center);
}
Print << tiles.size() << U" tiles";
if (tiles)
{
const auto it = std::min_element(tiles.begin(), tiles.end(), [cameraXZ = camera.getEyePosition().xz()](const auto& a, const auto& b)
{
return (a.second.xz().distanceFrom(cameraXZ) < b.second.xz().distanceFrom(cameraXZ));
});
Print << Morton::Decode2D32(it->first);
Sphere{ it->second, 0.5 }.draw(Linear::Palette::Red);
}
}
}
// 3D シーンを 2D シーンに描画する
{
Graphics3D::Flush();
renderTexture.resolve();
Shader::LinearToScreen(renderTexture);
}
if (SimpleGUI::Button(U"Random", Vec2{ 270, 10 }, 120))
{
// heightmap および heightmapTexture をランダムに生成する
terrain.fillRandomHeightmap();
// normalMap を更新する
terrainRenderer.updateNormalMapTexture(terrain);
}
if (Rect{ Terrain::MapSize }.mouseOver())
{
Cursor::RequestStyle(CursorStyle::Hand);
// 地形をブラシで編集する
if ((MouseL | MouseR).pressed())
{
// ブラシの中心座標
const Vec2 pos = Cursor::PosF();
// ブラシの大きさ
const double radiusScale = (MouseL.pressed() ? 2.0 : 1.0);
// ブラシの強さ
const double intensity = (Scene::DeltaTime() * 15.0);
// heightmapTexture を編集する
{
const ScopedRenderTarget2D target{ terrain.getHeightmapTexture() };
const ScopedRenderStates2D blend{ BlendState::Additive };
brushTexture.scaled(radiusScale).drawAt(pos, ColorF{ intensity });
}
// heightmap を更新する(heightmapTexture から読み込む重い処理なので、頻度を減らすことが望ましい)
{
Graphics2D::Flush();
terrain.readHeightmapFromHeightmapTexture();
}
// normalMap を更新する
terrainRenderer.updateNormalMapTexture(terrain);
}
}
if (SimpleGUI::Button(U"Clear", Vec2{ 270, 50 }, 120))
{
terrain.clear();
}
terrain.getHeightmapTexture().draw(ColorF{ 0.1 });
terrain.getNormalMapTexture().draw(0, 260);
}
}
IME の変換候補ウィンドウの描画(Windows 11)
# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
TextEditState state;
while (System::Update())
{
SimpleGUI::TextBox(state, Vec2{ 40, 40 }, 600);
if (state.active)
{
SimpleGUI::IMECandidateWindow(Vec2{ 40, 80 });
}
}
}
Point / Vec2 ::rotate90()
# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
Vec2 v{ 120, 200 };
while (System::Update())
{
ClearPrint();
Print << v;
if (MouseL.down())
{
v.rotate90(-1);
}
else if (MouseR.down())
{
v.rotate90(1);
}
Line{ Scene::Center(), (Scene::Center() + v) }.drawArrow(10, SizeF{ 20, 20 }, ColorF{ 0.25 });
}
}
SceneManager で最初のフェードイン時間を指定
# include <Siv3D.hpp>
using App = SceneManager<String>;
// タイトルシーン
class Title : public App::Scene
{
public:
Title(const InitData& init)
: IScene{ init }
{
Print << U"Title::Title()";
}
~Title()
{
Print << U"Title::~Title()";
}
void update() override
{
if (MouseL.down())
{
// ゲームシーンに遷移
changeScene(U"Game");
}
}
void draw() const override
{
Scene::SetBackground(ColorF{ 0.3, 0.4, 0.5 });
FontAsset(U"TitleFont")(U"My Game").drawAt(400, 100);
Circle{ Cursor::Pos(), 50 }.draw(Palette::Orange);
}
};
// ゲームシーン
class Game : public App::Scene
{
public:
Game(const InitData& init)
: IScene{ init }
, m_texture{ U"🐈"_emoji }
{
Print << U"Game::Game()";
}
~Game()
{
Print << U"Game::~Game()";
}
void update() override
{
if (MouseL.down())
{
// タイトルシーンに遷移
changeScene(U"Title");
}
}
void draw() const override
{
Scene::SetBackground(ColorF(0.2, 0.8, 0.6));
m_texture.drawAt(Cursor::Pos());
}
private:
Texture m_texture;
};
void Main()
{
FontAsset::Register(U"TitleFont", 60, Typeface::Heavy);
// シーンマネージャーを作成
App manager;
manager.add<Title>(U"Title");
manager.add<Game>(U"Game");
// "Title" シーンから開始。フェードインなし(0.0 秒)
manager.init(U"Title", 0.0s);
while (System::Update())
{
if (not manager.update())
{
break;
}
}
}