Skip to content

What's new in v0.4.3

1. ドロネー図、ボロノイ図の作成

ドロネー図、ボロノイ図の計算を行う Subdivision2D クラスが追加されました。

# include <Siv3D.hpp>

void Main()
{
    Window::Resize(1280, 720);
    Scene::SetBackground(ColorF(0.99));
    const Rect rect(50, 50, Scene::Size() - Size(100, 100));

    Subdivision2D subdiv(rect);

    // ドロネー三角形分割の三角形リスト
    Array<Triangle> triangles;

    // ボロノイ図の情報のリスト
    Array<VoronoiFacet> facets;

    // facets を長方形でクリップし Polygon に変換したリスト
    Array<Polygon> facetPolygons;

    while (System::Update())
    {
        const Vec2 pos = Cursor::Pos();

        // 長方形上をクリックしたら
        if (rect.leftClicked())
        {
            // 点を追加
            subdiv.addPoint(pos);

            // ドロネー三角形分割の計算
            subdiv.calculateTriangles(triangles);

            // ボロノイ図の計算
            subdiv.calculateVoronoiFacets(facets);

            // 長方形の範囲外をクリップ
            facetPolygons = facets.map([rect = rect.asPolygon()](const VoronoiFacet& f)
            {
                return Geometry2D::And(Polygon(f.points), rect).front();
            });
        }

        rect.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, 5, ColorF(0.6));
            Circle(nearestVertex, 16).drawFrame(3.5);
        }
    }
}

2. 長方形詰込み

長方形の集合を、別の大きな長方形に効率的に詰め込む問題を解決する std::pair<Array<Rect>, Size> RectanglePacking::Pack(const Array<Rect>& rects, int32 maxSide) 関数が追加されました。詰め込み後の長方形のリストと、それらを詰め込める最小の長方形のサイズのペアを返します。入力の rects の位置情報は無視されます。maxSide は幅または高さの最大値で、これに収まらない場合は空の配列と Size(0, 0) のペアを返します。配列に含まれる長方形の順番は、入力と出力で変わりません。

# include <Siv3D.hpp>

void Main()
{
    Scene::SetBackground(ColorF(0.99));

    // 詰め込む長方形
    const Array<Rect> input =
    {
        Rect(240, 210), Rect(500, 30), Rect(150, 120),
        Rect(60, 120), Rect(180, 60), Rect(120, 240)
    };

    // 詰め込みを計算
    const std::pair<Array<Rect>, Size> result = RectanglePacking::Pack(input, 600);

    while (System::Update())
    {
        Rect(result.second).draw(ColorF(0.7));

        for (auto [i, rect] : Indexed(result.first))
        {
            rect.draw(HSV(i * 40.0));
        }
    }
}

アニメーション

# include <Siv3D.hpp>

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

    for (auto& rect : rects)
    {
        const Point pos = RandomPoint(Rect(0, 0, 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, output;
    Size size(0, 0);
    Point offset(0, 0);
    Stopwatch s;

    while (System::Update())
    {
        if (!s.isStarted() || s > 1.8s)
        {
            input = GenerateRandomRects();
            std::tie(output, size) = RectanglePacking::Pack(input, 1024);

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

            s.restart();
        }

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

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

        for (auto i : step(input.size()))
        {
            const auto& in = input[i];
            const auto& out = output[i];
            const Vec2 pos = in.pos.lerp(out.pos, e);
            const RectF rect(pos, out.size);
            rect.scaledAt(rect.center(), k)
                .draw(HSV(i * 25.0, 0.65, 0.9))
                .drawFrame(2, 0, ColorF(0.25));
        }
    }
}

3. GIF アニメーション読み込み

GIF アニメーションファイルを読み込み、一連のフレームの Image と、フレームごとの表示時間を取得する AnimatedGIFReader クラスが追加されました。

# include <Siv3D.hpp>

// アニメーション描画用のクラス
struct AnimationTexture
{
    Array<Texture> textures;

    // フレームの時間
    Array<int32> delays;

    int32 duration = 0;

    explicit operator bool() const noexcept
    {
        return !textures.isEmpty();
    }

    Size size() const noexcept
    {
        if (!textures)
        {
            return Size(0, 0);
        }

        return textures.front().size();
    }

    size_t frames() const noexcept
    {
        return textures.size();
    }

    size_t getFrameIndex(int32 timeMillisec) const noexcept
    {
        return AnimatedGIFReader::MillisecToIndex(timeMillisec, delays, duration);
    }

    const Texture& getTexture(int32 timeMillisec) const noexcept
    {
        return textures[getFrameIndex(timeMillisec)];
    }
};

void Main()
{
    AnimationTexture animation;
    {
        // GIF ファイルを開く
        const AnimatedGIFReader gif(U"example/test.gif");

        if (!gif)
        {
            throw Error(U"Failed to open a gif file");
        }

        Array<Image> images;

        // GIF アニメーションを読み込み
        if (gif.read(images, animation.delays, animation.duration))
        {
            // Image を Texture に変換
            animation.textures = images.map([](const Image& i) { return Texture(i); });
        }
        else
        {
            throw Error(U"Failed to load a gif animation");
        }
    }

    // 画像のサイズ、フレーム数、アニメーションの長さ(ミリ秒)
    Print << U"{}, {} frames ({} ms)"_fmt(animation.size(), animation.frames(), animation.duration);

    const Point pos(10, 90);

    while (System::Update())
    {
        const int32 timeMillisec = static_cast<int32>(Scene::Time() * 1000);

        animation.getTexture(timeMillisec).draw(pos);
    }
}

4. Rect::rounded() で 4 つの角に異なる値を指定可能に

Rect::rounded() に、長方形の左上、右上、右下、左下で異なる値を指定するオーバーロードが追加されました。

# include <Siv3D.hpp>

void Main()
{
    Scene::SetBackground(ColorF(0.3));

    Array<Rect> rects;

    for (auto p : step(Size(3, 4)))
    {
        rects << Rect(p * Size(220, 140), 180, 100).movedBy(80, 40);
    }

    while (System::Update())
    {
        rects[0].rounded(30, 0, 0, 0).draw(HSV(20, 0.75, 1.0));
        rects[1].rounded(30, 30, 0, 0).draw(HSV(40, 0.75, 1.0));
        rects[2].rounded(0, 30, 0, 0).draw(HSV(60, 0.75, 1.0));

        rects[3].rounded(30, 0, 0, 30).draw(HSV(80, 0.75, 1.0));
        rects[4].rounded(10, 20, 30, 40).draw(HSV(100, 0.75, 1.0));
        rects[5].rounded(0, 30, 30, 0).draw(HSV(120, 0.75, 1.0));

        rects[6].rounded(100, 0, 0, 0).draw(HSV(140, 0.75, 1.0));
        rects[7].rounded(100, 0, 100, 0).draw(HSV(160, 0.75, 1.0));
        rects[8].rounded(0, 0, 100, 0).draw(HSV(180, 0.75, 1.0));

        rects[9].rounded(100, 0, 0, 20).draw(HSV(200, 0.75, 1.0));
        rects[10].rounded(100, 20, 100, 20).draw(HSV(220, 0.75, 1.0));
        rects[11].rounded(0, 20, 100, 0).draw(HSV(240, 0.75, 1.0));
    }
}

5. SimpleGUI::HorizontalRadioButtons()

水平に並んだラジオボタン SimpleGUI::HorizontalRadioButtons() が SimpleGUI に追加されました。

# include <Siv3D.hpp>

void Main()
{
    Scene::SetBackground(ColorF(0.8, 0.9, 1.0));

    const Array<String> options = { U"Windows", U"macOS", U"Linux" };
    size_t indexA = 0;
    size_t indexB = 0;

    while (System::Update())
    {
        // 水平
        SimpleGUI::HorizontalRadioButtons(indexA, options, Vec2(20, 20));

        // 縦
        SimpleGUI::RadioButtons(indexB, options, Vec2(20, 60));
    }
}

6. Math::InvLerp()

  • Math::Lerp(begin, end, t) == value
  • Math::InvLerp(begin, end, value) == t

となるような値 t を求める Math::InvLerp() が追加されました。

# include <Siv3D.hpp>

void Main()
{
    Scene::SetBackground(ColorF(0.6, 0.8, 0.7));
    const Font font(40, Typeface::Bold);

    const double begin = 240.0;
    const double end = 450.0;

    while (System::Update())
    {
        const double value = Cursor::Pos().y;

        // Math::Lerp(begin, end, t) == value になる値 t を求める
        const double t = Math::InvLerp(begin, end, value);

        // 値を [0.0, 1.0] の範囲に収める
        const double st = Saturate(t);

        font(st).draw(20, 20);

        Line(Vec2(0, begin), Arg::direction(Scene::Width(), 0)).draw(2, ColorF(0.5));
        Line(Vec2(0, end), Arg::direction(Scene::Width(), 0)).draw(2, ColorF(0.5));

        Circle(Cursor::Pos(), 50).draw(ColorF(st));
    }
}

7. Line のコンストラクタ追加

名前付き引数を使った Line のコンストラクタが 2 種類追加されました。Line(pos, pos + dir) のように pos を 2 回書く必要がなくなります。

# include <Siv3D.hpp>

void Main()
{
    while (System::Update())
    {
        // 始点の位置、始点から見た終点の方向、終点までの距離
        Line(Scene::Center(), Arg::angle = 45_deg, 200)
            .draw(LineStyle::RoundCap, 10);

        // 始点の位置、終点までのベクトル
        Line(Scene::Center(), Arg::direction = Vec2(0, 200))
            .draw(LineStyle::RoundCap, 10, Palette::Orange);
    }
}

8. Rect::drawFrame(), Circle::drawPie(), Circle::drawArc() の 2 色指定

Rect::drawFrame(), Circle::drawPie(), Circle::drawArc() に、内側の色と外側の色を別々に指定するオーバーロードが追加されました。

# include <Siv3D.hpp>

void Main()
{
    Scene::SetBackground(Palette::White);

    while (System::Update())
    {
        // 内側 ColorF(0.1, 0.6, 0.3), 外側 ColorF(0.6, 1.0, 0.8)
        Rect(50, 50, 300)
            .drawFrame(30, ColorF(0.1, 0.6, 0.3), ColorF(0.6, 1.0, 0.8));

        // 内側 HSV(50), 外側 HSV(0)
        Circle(200, 200, 100)
            .drawPie(0_deg, 120_deg, HSV(50), HSV(0));

        // 内側 Palette::White, 外側 Palette::Black
        Circle(200, 200, 100)
            .drawArc(180_deg, 120_deg, 10, 10, Palette::White, Palette::Black);
    }
}

9. ZIP アーカイブの読み込み

ZIP アーカイブ (.zip) の中身の取得や展開を行う ZIPReader クラスが追加されました。ZIPReader::extractToMemory() を使うと、ファイルをメモリ上で展開して TextureAudio などを作成できます。

  • Windows で作成された Shift-JIS エンコードの ZIP アーカイブに含まれる日本語ファイル名は、Windows 以外の環境では正しく扱えません
  • 日本語ファイル名をあらゆるプラットフォームで正しく扱いたい場合、UTF-8 エンコードで ZIP アーカイブを作成してください (7-zip の場合は cu=on オプションをつける)

# include <Siv3D.hpp>

void Main()
{
    const ZIPReader zip(U"example/zip/zip_test.zip");

    // 含まれているファイルやディレクトリの列挙
    for (const auto& path : zip.enumPaths())
    {
        Print << path;
    }

    // `zip_test/loremipsum.txt` を `unzipped1/` フォルダに展開
    zip.extract(U"zip_test/loremipsum.txt", U"unzipped1/");

    // `zip_test/image/` に含まれているすべてのファイルを `unzipped2/` フォルダに展開
    zip.extract(U"zip_test/image/*", U"unzipped2/");

    // すべてを `unzipped3/` フォルダに展開
    zip.extractAll(U"unzipped3/");

    // `zip_test/image/windmill.png` をメモリ上で展開してテクスチャを作成
    const Texture textureA(zip.extractToMemory(U"zip_test/image/windmill.png"));

    // `zip_test/image/siv3d-kun.png` をメモリ上で展開してテクスチャを作成
    const Texture textureB(zip.extractToMemory(U"zip_test/image/siv3d-kun.png"));

    while (System::Update())
    {
        textureA.draw();
        textureB.draw();
    }
}

10. 不正な Polygon 頂点の自動修正

手入力などによる不正な Polygon の頂点を修正し、妥当な Array<Polygon> に変換する機能が追加されました。

# include <Siv3D.hpp>

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

    const Font font(20, Typeface::Bold);

    Array<Vec2> points;
    Array<Polygon> solvedPolygons;

    while (System::Update())
    {
        if (MouseL.down())
        {
            points << Cursor::Pos();

            // 頂点列から適切な Polygon を作成
            solvedPolygons = Polygon::Correct(points, {});
        }
        else if (MouseR.down())
        {
            points.clear();
            solvedPolygons.clear();
        }

        for (auto [i, point] : Indexed(points))
        {
            Circle(point, 5).draw();
            Line(points[i], points[(i + 1) % points.size()])
                .drawArrow(2, Vec2(20, 20), Palette::Orange);
        }

        font(points).draw(Rect(20, 20, 600, 720));

        {
            Transformer2D trans(Mat3x2::Translate(640, 0));

            font(solvedPolygons).draw(Rect(20, 20, 600, 720));

            for (auto [i, solvedPolygon] : Indexed(solvedPolygons))
            {
                const HSV color(i * 40.0, 0.7, 1.0);
                solvedPolygon.draw(color);

                const auto& outer = solvedPolygon.outer();

                for (auto [k, point] : Indexed(outer))
                {
                    const Vec2 begin = outer[k];
                    const Vec2 end = outer[(k + 1) % outer.size()];
                    const Vec2 v = (end - begin).normalized();
                    const Vec2 c = (begin + end) / 2;
                    const Vec2 oc = c + v.rotated(-90_deg) * 10;
                    Line(oc - v * 20, oc + v * 20)
                        .drawArrow(2, Vec2(10, 10), color);
                }
            }
        }
    }
}

11. Direct3D ドライバ / デバイスの種類の変更

Windows 版で #include <Siv3D.hpp> の前に特別なマクロを定義すると、アプリケーションが使用する Direct3D ドライバーの種類を WARP, Reference などに変更できるようになりました。GPU のドライバの問題で正常な描画ができない場合に WARP によるたソフトウェアレンダリングを使用してください。描画負荷が軽いアプリケーションであれば、WARP で動かすプログラムをリリースすることも選択肢となります。これらのフラグは重複して指定することはできません。

// ソフトウェアレンダラ―を使用 (Windows でのみ有効)
# define SIV3D_WINDOWS_D3D_DRIVER_TYPE_WARP

// iGPU (Intel UHD Graphics など) 優先 (Windows でのみ有効)
//# define SIV3D_WINDOWS_D3D_DRIVER_TYPE_HARDWARE_FAVOR_INTEGRATED

// リファレンスドライバを使用 (Windows でのみ有効)
//# define SIV3D_WINDOWS_D3D_DRIVER_TYPE_REFERENCE

// Siv3D.hpp よりも前で定義
# include <Siv3D.hpp>

void Main()
{
    Scene::SetBackground(ColorF(0.8, 0.9, 1.0));

    const Texture texture(U"example/windmill.png");

    while (System::Update())
    {
        texture.draw();
    }
}

12. その他

  • Image to Polygon の堅牢性が向上し、クラッシュしなくなりました
  • Linux 版のビルドで AngelScript のリンクが不要になりました
  • macOS と Linux の一部環境で Microphone の初期化に失敗することがあった問題を修正しました
  • isOpened() というメンバ関数は isOpen() に名前が変更されました
  • zlib の圧縮展開を行う Zlib::Compress(), Zlib:: Decompress() を追加しました
  • ParseOpt<float>() が例外を投げることがあった問題を修正しました
  • Math::InvSqrt2_v が正しくなかったのを修正しました
  • Visual Studio 用のプロジェクトテンプレートにタグを指定しました
  • Visual Studio のプロジェクト作成時に Main.cpp が自動で開くようにしました
  • Windows 用プロジェクトの Icon.ico を icon.ico にリネームしました
  • Camera2DScene::Size() 依存を解消しました
  • ParticleSystem2DParameters の仕様を改善しました
  • 各種 RNG のシリアライズ、デシリアライズを実装しました
  • Serial が切断されても isOpen()true を返していた問題を修正しました
  • RoundRect の頂点生成品質の問題を修正しました
  • DynamicTexture でサイズとフォーマットのみ指定した際のエラーを修正しました
  • macOS で日本語パスを扱うと一部の関数がクラッシュすることがあった問題を修正しました
  • Windows で Graphics::SetTargetFrameRateHz() が大きく不正確になることがあった問題を修正しました
  • RenderTexture のコンストラクタでは、特に明示しなければ ColorF(0.0, 1.0) で中身をクリアするよう仕様変更しました
  • JSONWriter::write(bool) の挙動が正しくなかった問題を修正しました
  • BasicCamera3Dexperimental:: を外しました
  • その他軽微な修正多数