【Unity】【UI Toolkit】Painter2Dにおいてパス同士の内外判定を決めるFill Rulesの使い方

UnityのUI ToolkitのPainter2Dにおいて、パス同士の内外判定を決めるFill Rulesの使い方についてまとめました。

パス同士の内外判定とは?

以下のようなシンプルなパスを考えた時、パスの「内側」がどこであるかは自明です。
パスに囲まれた部分が内側になります。

シンプルなパス

では以下の図形における「内側」はどこでしょうか?

「内側」とは?

UI ToolkitのPainter2DのFillメソッドでは図形の内側を塗りつぶすことができますが、その際にどのように塗りつぶすべき「内側」を判定するかを指定することができます。

本記事ではこの方法について説明します。

なおPainter2Dの基礎知識については以下の記事にまとめていますので、必要に応じて参照してください。

light11.hatenadiary.com

ベクターグラフィックスにおける内外判定

UnityはSVGなどのベクターグラフィックスで使われる内外判定の方法に従った実装をしているため、まずはこれを説明します。

まず前節にも載せた以下の図形について考えて行きます。

図形

まず最初のステップとして、パスを引いた順番の通りにパスの「方向」を決めます。
下図では便宜的にこの方向を矢印で示しています。

「方向」を決める

次に内外判定する点を決めます。今回は下の図における赤いポイントの内外判定を行います。

内外判定する点を決める

次のステップとして、求めたい点から任意の方向に向けて直線を引きます。
また、この直線と各パスが交差する点を求めます。下図では青い点で示しています。

内外判定する点から任意の直線を引く

これらの点について、赤い点から見たときにパスが左から右に横切る場合には+1、右から左に横切る場合は-1します。そしてその総和を求めます。

総和を求める

上の図では総和は2となります。

最後にFill Ruleを適用します。
UI Toolkitはnonzeroevenoddという二つのFill Ruleに対応しています。

まずFill Ruleをnonzeroに設定した場合には、「総和がゼロではない部分が全て内側」になります。
今回の例では、パスに囲まれたどの地点も総和は1あるいは2になるので、全て内側と判定されます。
よってFillの結果は以下の通りとなります。

nonzero

一方でevenoddは「総和が奇数である部分が全て内側」になります。
今回の図形では、図形が重なる部分は総和が2なので外側、重なっていない部分は総和が1なので内側と判定されます。
よってFillの結果は以下の通りとなります。

evenodd

UI Toolkitで動作確認する

理論が理解できたところで、実際にUI Toolkitを使って動作確認していきます。
以下のように上記の例と同じく正方形と円形のパスを作成し、塗りつぶしを行います。

using UnityEngine;
using UnityEngine.UIElements;

public sealed class ExampleElement : VisualElement
{
    public ExampleElement()
    {
        generateVisualContent += OnGenerateVisualContent;
    }

    private static void OnGenerateVisualContent(MeshGenerationContext context)
    {
        var painter = context.painter2D;

        painter.fillColor = Color.blue;
        painter.BeginPath();

        // 正方形
        painter.MoveTo(new Vector2(100, 100));
        painter.LineTo(new Vector2(200, 100));
        painter.LineTo(new Vector2(200, 200));
        painter.LineTo(new Vector2(100, 200));
        painter.ClosePath();

        // 円
        painter.MoveTo(new Vector2(250, 200));
        painter.Arc(new Vector2(200, 200), 50, Angle.Degrees(0), Angle.Degrees(360));

        // nonzeroで塗りつぶし
        painter.Fill(FillRule.NonZero);
    }
}

まずはFillの引数にNonZeroを指定して塗りつぶしています。

これを適当なウィンドウに描画します。

using UnityEditor;

public sealed class ExampleWindow : EditorWindow
{
    public void CreateGUI()
    {
        var element = new ExampleElement();
        element.style.width = 1000;
        element.style.height = 1000;
        rootVisualElement.Add(element);
    }

    [MenuItem("Window/Example")]
    private static void Open()
    {
        GetWindow<ExampleWindow>();
    }
}

Window > Exampleからウィンドウを開くと下図の結果が得られます。

NonZeroの結果

次に、Fill RuleをOddEvenに変更します(前述のevenoddと同じ意味です)。

painter.Fill(FillRule.OddEven);

すると描画結果が以下のように変わります。

OddEvenの結果

正常に動作していることを確認できました。

参考

light11.hatenadiary.com

参考

triple-underscore.github.io