Polygonからの距離を算出する関数を活用する【OpenSiv3D】


OpenSiv3D には Polygon からの距離を取得する関数 Geometry2D::Distance があります。この関数を使うと様々なエフェクトを作成したり、数値最適化に活用できます。今回は、マウスで描いたポリゴンから距離を算出して、等高線を描くサンプルを紹介します。

Main.cpp
# include <Siv3D.hpp>

void Main()
{
    // キャンバスのサイズ
    constexpr Size canvasSize(800, 600);

    // ペンの太さ
    constexpr int32 thickness = 4;

    // 書き込み用の画像データを用意
    Image image(canvasSize, Color(0, 0));

    // 表示用のテクスチャ(内容を更新するので DynamicTexture)
    DynamicTexture texture(image);

    // 作成した Polygon の配列
    Array<Polygon> polygons;


    //等高線
    Array<MultiPolygon> contours;

    //等高線の数
    constexpr size_t contourNum = 20;

    //等高線の感覚
    constexpr double offset = 20.0;

    while (System::Update())
    {
        // ペンの色
        const Color penColor = HSV(polygons.size() * 20);

        if (MouseL.pressed())
        {
            // 書き込む線の始点は直前のフレームのマウスカーソル座標
            // (初回はタッチ操作時の座標のジャンプを防ぐため、現在のマウスカーソル座標にする)
            const Point from = MouseL.down() ? Cursor::Pos() : Cursor::PreviousPos();

            // 書き込む線の終点は現在のマウスカーソル座標
            const Point to = Cursor::Pos();

            // image に線を書き込む
            Line(from, to).overwrite(image, thickness, penColor);

            // 書き込み終わった image でテクスチャを更新
            texture.fill(image);
        }
        else if (MouseL.up())
        {
            // 画像の非透過部分から Polygon を作成(穴無し)
            if (const Polygon polygon = image.alphaToPolygon(1, false))
            {
                // 少し単純化してから追加
                polygons << polygon.simplified(2.0);
            }                           


            {               
                Array<double> distances;
                Grid<double> distanceGrid(image.size());
                contours.clear();

                for (auto p : step(distanceGrid.size()))
                {
                    for (const auto& polygon : polygons)
                    {
                        distances << Geometry2D::Distance(p, polygon);
                    }

                    const double distance = *std::min_element(distances.begin(), distances.end());

                    distanceGrid[p.y][p.x] = distance;

                    distances.clear();
                }

                for (size_t i = 0; i < contourNum; ++i)
                {
                    // 画像データをリセット
                    image.fill(Color(0, 0));

                    for (auto p : step(distanceGrid.size()))
                    {
                        if (distanceGrid[p] < offset * i)
                        {
                            image[p] = penColor;
                        }
                    }

                    // 画像の非透過部分から Polygon を作成(穴無し)
                    if (const MultiPolygon polygon = image.alphaToPolygons(1, false))
                    {
                        // 少し単純化してから追加
                        contours << polygon.simplified(2.0);
                    }
                }
            }


            image.fill(Color(0, 0));
            // テクスチャを更新
            texture.fill(image);
        }


        //それぞれの 等高線を描画
        for (size_t i = 0; i < contours.size(); ++i)
        {
            contours[contours.size() - 1 - i].draw(HSV(i * 20, 0.4, 1.0)).drawFrame(4, HSV(i * 20));
        }


        // それぞれの Polygon を描画
        for (auto [i, polygon] : Indexed(polygons))
        {
            polygon.draw(HSV(i * 20, 0.4, 1.0))
                .drawFrame(4, HSV(i * 20));
        }

        // テクスチャを表示
        texture.draw();
    }
}