gd_cubism icon indicating copy to clipboard operation
gd_cubism copied to clipboard

プログラム実行時、モデルの動きにCollisionPolygon2Dを追従させたい

Open creeper-0910 opened this issue 2 years ago • 15 comments
trafficstars

live2dモデルを使用する際に、モデルの形や動きに合わせたCollisionPolygon2Dを生成したいと考えているのですが、可能でしょうか? お時間のあるときに確認していただけると幸いです。 (下記は手動で作成したものですが、実行時のモデルの動きに追従させることができなかったため使用できませんでした。) image

creeper-0910 avatar Oct 16 '23 01:10 creeper-0910

@creeper-0910 CollisionPolygon2Dの目的というのは、例えば口をクリックするとしゃべって、頭をドラッグすると笑顔になるといったインタラクションを目的とするものでしょうか。

もしそうでしたら、 CollisionPolygon2D を用意しなくても指定した座標とLive2D内のHitAreaやTriangleとのコリジョンを取る事が出来ますよ。

こちらはまだ API として公開する形を決めかねているのですけど、実装自体は完了していまして、 0.3系のデモに demo_effect_hit_area.tscn というサンプルを追加しています。

こちらは以下の要望を満たすために用意しました。

  • あらかじめ Live2DのEditor 上で HitArea を用意しておき、 Godot Engine 上でその場所をクリックしたらシグナルを発行できるようにしたい。
  • HitArea かどうかは関係なく、 Live2Dモデル上の任意の場所をクリック出来る様にしたい。

画面としては以下の様なものなのですが、動かしてみてもらえると理解しやすいと思います。お手元の 0.3 をアップデートして再ビルドを行うと利用可能になります。

hitarea

そうではなく、単純に塗りつぶしたポリゴンが欲しいという場合でしたら、 GDCubismUserMode の get_meshes から計算で得る方法もあります。 get_meshes で得られるデータは実際に描画に使用した頂点情報が格納されていますので、パーツ単位でよければ完全に一致した情報が得られます。

Live2Dの場合ですと、毎フレーム頂点を作り直しますので非力な環境だと辛いかもしれません。

もうすこし具体的な目的が教えていただければ、より適した方法が提案出来るかもしれません。

MizunagiKB avatar Oct 16 '23 01:10 MizunagiKB

返信ありがとうございます、 デスクトップマスコットを作成しており、ウィンドウ全体の背景透過とクリックスルーのために、CollisionPolygon2Dを利用したいと考えております。

creeper-0910 avatar Oct 16 '23 01:10 creeper-0910

@creeper-0910

デスクトップマスコット風にしたいのでしたら、 Godot Engine の本体機能を使って実現できますよ。

元々 3.5 で使えたものが 4.0 では不具合があったようです。 4.1 では動作しています。

GDCubism では demo/addons/gd_cubism/example/demo_transparent.tscn というサンプルを用意していますので、そちらを実行してみてください。

具体的には以下の様なスクリプトを使って実現できます。

var enable_transparent: bool = true
get_tree().root.transparent_bg = enable_transparent
# 描画していない場所を透過
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_TRANSPARENT, enable_transparent, 0)
# 常に最前面に表示
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_ALWAYS_ON_TOP, true, 0)

Window が透明になっても見た目が透けているだけで Window 枠は依然として存在しています。 ですので、Window 枠をキャラクターぎりぎりのサイズにするといった工夫をしないとデスクトップマスコットとしてはちょっと使いづらいかも?

DisplayServer を参照していただくと、他にもクリップボード制御やアイテムのドロップ等に関する情報を見つける事が出来ます。

screen

以下はXに貼り付けている動画(内容は上のスクリーンショットと同じもの)です。 https://twitter.com/MizunagiKB/status/1701230124186714597

MizunagiKB avatar Oct 16 '23 03:10 MizunagiKB

@creeper-0910

こちらClickを除外したいという部分を失念していました。

画像データの透過処理は上で述べている方法で解決できますので、余計な部分にマウスを反応させたくない、というのをどうするかとなりますね。

処理としては DisplayServer.window_set_mouse_passthrough 関数に渡す PackedVector2Array をどう生成するかとなり、アプローチとしては以下の様なものが考えられます。

  1. GDCubismUserModelget_meshes 関数を使ってメッシュ情報を取得します。
  2. そこから代表的なメッシュのみを取り出します。
  3. 凸包というアルゴリズムを使って、頂点を囲むポリゴンを生成します。

提示されている画像では Live2Dモデルを綺麗にくり抜いていますが、自動生成で行おうとするとやや手間となりますので、ここでは周囲を囲むポリゴンを作る問題に置き換えます。

以下の様なイメージです。

fig2

凸包について

もしかしたら考える楽しさを奪ってしまうかもしれませんが、説明するのはちょっと難しいのでここではそういうものがあると捉えてください。

ここでは凸包を求めるのに Graham Scan という手法を用います。 コーディングは以下のサイトのものを Godot Engine 向けに移植しました。

  • https://tjkendev.github.io/procon-library/
    • https://tjkendev.github.io/procon-library/cpp/geometry/graham_scan.html

GDScript で記述すると以下の様になります。

func check_cross(ary_check: Array, v: Vector2) -> bool:
    var va: Vector2 = ary_check[ary_check.size() - 2]
    var vb: Vector2 = ary_check[ary_check.size() - 1]

    return (((vb.x - va.x) * (v.y - va.y)) - ((vb.y - va.y) * (v.x - va.x))) > 0


func convex_hull(ary_vertex: Array) -> Array:
    ary_vertex.sort()

    var ary_result: Array
    var n = ary_vertex.size()
    
    for vtx in ary_vertex:
        while ary_result.size() > 1 and check_cross(ary_result, vtx):
            ary_result.pop_back()
        ary_result.push_back(vtx)

    var t = ary_result.size()

    var i = n - 2
    while i >= 0:
        var vtx: Vector2 = ary_vertex[i]
        while ary_result.size() > t and check_cross(ary_result, vtx):
            ary_result.pop_back()
        ary_result.push_back(vtx)

        i -= 1

    ary_result.pop_back()

    return ary_result

この関数を _process 関数内で呼び出して、 window_set_mouse_passthrough 関数に渡します。

func _process(delta):

    var dict_mesh = $Sprite2D/GDCubismUserModel.get_meshes()
    var ary: PackedVector2Array
    # ArtMesh121 といった名称はLive2Dモデル毎に異なります。
    ary += dict_mesh["ArtMesh121"].surface_get_arrays(0)[Mesh.ARRAY_VERTEX]
    ary += dict_mesh["ArtMesh122"].surface_get_arrays(0)[Mesh.ARRAY_VERTEX]
    ary += dict_mesh["ArtMesh135"].surface_get_arrays(0)[Mesh.ARRAY_VERTEX]
    ary += dict_mesh["ArtMesh146"].surface_get_arrays(0)[Mesh.ARRAY_VERTEX]
    ary += dict_mesh["ArtMesh147"].surface_get_arrays(0)[Mesh.ARRAY_VERTEX]
    ary += dict_mesh["ArtMesh231"].surface_get_arrays(0)[Mesh.ARRAY_VERTEX]
    ary += dict_mesh["ArtMesh278"].surface_get_arrays(0)[Mesh.ARRAY_VERTEX]
    var ary_poly = convex_hull(ary)

    # ここでは Polygon2D がノードツリーに存在しているとしています。
    # 直接 PackedVector2Array を渡せますが、 Polygon2D を経由すると表示が確認できるので。
    $Polygon2D.polygon = PackedVector2Array(ary_poly)

    DisplayServer.window_set_mouse_passthrough($Polygon2D.polygon)

結構重たい処理になりますので、基本姿勢の状態を一回だけ生成したり HitArea のみを生成するといった方法を併用するのが良さそうです。

参考になりましたらどうぞ。

MizunagiKB avatar Oct 16 '23 12:10 MizunagiKB

@creeper-0910 こちらのコードですが、頂点情報の取り扱いサンプルとしてもわかりやすいものだと感じましたので 0.3 に取り込みました。

demo / addons / gd_cubism / example に保存ざれている demo_transparent.tscn を動かして貰えると、手元で実際の挙動が確認出来るかと思います。

MizunagiKB avatar Oct 19 '23 01:10 MizunagiKB

遅くなってしまい申し訳ございません。 ありがとうございます!確認してみます

creeper-0910 avatar Oct 22 '23 11:10 creeper-0910

こちらのIssue、だいぶ経過していますどうでしょうか。 洗練されたコードではありませんが特に問題はないと思いますのでクローズ致します。

MizunagiKB avatar Dec 16 '23 23:12 MizunagiKB

かなり期間が開いてしまい申し訳ございません。 godot4.3で試したところ、Polygon2Dの範囲外となる部分のモデルが透過されてしまうようですが、これは仕様でしょうか...? お手数をお掛けして申し訳ないのですが、確認していただけると嬉しいです。 image

creeper-0910 avatar Sep 20 '24 05:09 creeper-0910

報告ありがとうございます。 まだ確認できていませんが Issue を Reopenしておきました。

MizunagiKB avatar Sep 24 '24 00:09 MizunagiKB

macOS 上で demo_transparent.tscn を Godot Engine 4.3 + GDCubism 0.7 で動かしてみました。 ひとまずは上記の環境においては正常に表示される様です。

後ほど時間を作って他の環境でも試してみます。

ss

MizunagiKB avatar Sep 24 '24 23:09 MizunagiKB

macOS 上で demo_transparent.tscn を Godot Engine 4.3 + GDCubism 0.7 で動かしてみました。 ひとまずは上記の環境においては正常に表示される様です。

後ほど時間を作って他の環境でも試してみます。

This is likely a windows limitation - Window - mouse_passthrough_polygon

Note: On Windows, the portion of a window that lies outside the region is not drawn, while on Linux (X11) and macOS it is.

Note: This property is implemented on Linux (X11), macOS and Windows.

There was another attempt at mouse passthrough using just transparent pixels of the viewport via Windows API calls which may have to be applied here: https://github.com/KitzuGG/Godot-Clickthrough

Other noteworthy issues: https://github.com/godotengine/godot/issues/76167#issuecomment-2042425498

Haledire avatar Sep 25 '24 06:09 Haledire

@Haledire @creeper-0910 Thank you for the information.

I initially thought that the window_set_mouse_passthrough property was meant to specify the area for receiving mouse events, but it turns out it actually specifies the drawing area.

This was my misunderstanding. I apologize for the confusion. It appears to be rendered on macOS, but I did not fully understand the underlying issue.

If we apply the previously mentioned convex hull to all ArtMeshes, we would obtain a rather large Polygon2D that covers all drawing areas. However, this would be a very heavy process to perform every frame.

Alternatively, we could consider the following methods:

  • Manually create a Polygon2D in the Godot Engine Editor.
  • Create several HitAreas in the Live2D Cubism Editor and then determine a polygon (convex hull or AABB) that encompasses them.

Does this look good to you?


情報ありがとうございます。 window_set_mouse_passthroughプロパティというのは、マウスイベントを受け付ける領域指定をするものだと思っていたのですが、実際には描画範囲を指定するものだっということですね。

これは私が勘違いしていました。申し訳ありません。 macOSだと描画される様ですが、前提となる課題をうまく理解できていない状態でした。

前述した凸包を全てのArtMeshに対して行えば、全ての描画領域をカバーするかなり大きな Polygon2D を得られるとは思いますが、少なくとも毎フレーム行うにはかなり重い処理となります。

それ以外の方法でしたら、以下の様な方法でしょうか。

  • Godot Engine の Editor 上で手動でPolygon2Dを作っておく。
  • Live2D Cubism Editor上である程度のHitAreaをいくつか作っておいて、それらを包むポリゴン(凸包かAABB)を求める。

高速に描画領域を包む Polygon2D を生成するというのはなかなか難しそうです。

MizunagiKB avatar Sep 27 '24 04:09 MizunagiKB

I have reviewed the Godot Engine source code and noticed a discrepancy between the names and functions.

macOS https://github.com/godotengine/godot/blob/76a135926aef1f02f27e4e09093787f2c670956d/platform/macos/display_server_macos.mm#L3037-L3059

Windows https://github.com/godotengine/godot/blob/76a135926aef1f02f27e4e09093787f2c670956d/platform/windows/display_server_windows.cpp#L1786

Linux (X11) https://github.com/godotengine/godot/blob/76a135926aef1f02f27e4e09093787f2c670956d/platform/linuxbsd/x11/display_server_x11.cpp#L1943-L1961

Upon examining each implementation, it appears that on macOS, the function filters mouse events within a specified range, as the name suggests. However, on Windows and Linux, the function clips the drawing area.

Windows: SetWindowRgn, X11: XShapeCombineRegion

While both filtering mouse events and clipping the drawing area achieve the same goal of “not accepting mouse events within a region,” the processes and purposes are entirely different, making the current naming inappropriate.

Nevertheless, it seems that this is the intended behavior in Godot Engine.

Although I have not verified the behavior, based on the API documentation, it appears that X11 would behave similarly to Windows.

MizunagiKB avatar Sep 27 '24 15:09 MizunagiKB

@creeper-0910 文章がちらかってしまいましたので一旦まとめました。

現在の状況について

デスクトップマスコットを実装するアプローチとしては Window の透明化とマウスイベントのフィルタリング(クリックスルー)を用いるというのは妥当なアプローチのようです。

そして、マウスイベントのフィルタリング処理を行うために、 mouse_passthrough_polygon を使用すると Windows 版の Godot ではマウスイベントのフィルタリングだけでなく、描画領域のクリッピングまで行われてしまう。

という状況となっています。

解決方法について

@Haledire さんが解決方法の一つを提案されています。

他にも元々のアプローチである手動でPolygon2Dを用意する、描画領域を全て包む様なPolygon2Dを動的に生成する、というものが考えられるのですが、労力や処理負荷という面からはあまりお薦めできません。

問題の技術的背景について

私の方で Godot Engine のソースを読んだところ、 Windows 版での mouse_passthrough_polygon の実装に問題がある事に気がつきました。

Windows版の Godot Engine で mouse_passthrough_polygon が 正しく動作していない原因は、マウスイベントのフィルタリングをWindowを切り抜く処理である SetWindowRgn 関数でおこなっているためでした。

より望ましい動作にするには、 /platform/windows/display_server_windows.cpp 内の処理を修正することとなります。

具体的には macOS 版の実装を参考に、 DisplayServerWindows::WndProc 内で Geometry2D::is_point_in_polygon による領域判定を行い、範囲外であれば DefWindowProc 関数を呼び出す様にします。

こちらは受け取って貰えないかもしれませんが、 Godot の Issue に投稿しようと思います。

具体的な解決が出来ずに申し訳ありません。


@creeper-0910 Things have become a bit cluttered, so I have summarized the current situation.

Current Situation

Using window transparency and mouse event filtering (click-through) seems to be a reasonable approach for implementing a desktop mascot.

However, when using mouse_passthrough_polygon for mouse event filtering, it not only filters mouse events but also clips the drawing area in the Windows version of Godot.

Proposed Solutions

@Haledire has suggested one possible solution.

Other approaches include manually preparing a Polygon2D or dynamically generating a Polygon2D that encompasses the entire drawing area. However, these are not recommended due to the labor and processing load involved.

Technical Background of the Issue

Upon reviewing the Godot Engine source code, I noticed an issue with the implementation of mouse_passthrough_polygon in the Windows version.

The reason mouse_passthrough_polygon does not work correctly in the Windows version of Godot is that mouse event filtering is performed using the SetWindowRgn function, which clips the window.

To achieve the desired behavior, the processing in /platform/windows/display_server_windows.cpp needs to be modified.

Specifically, by referring to the macOS implementation, we can perform region checks using Geometry2D::is_point_in_polygon within DisplayServerWindows::WndProc and call the DefWindowProc function if the point is outside the region.

I am considering posting this on the Godot Issue tracker, although I am not sure if it will be accepted.

I apologize for not being able to provide a concrete solution.

MizunagiKB avatar Sep 28 '24 23:09 MizunagiKB

返信遅くなってしまい申し訳ございません。 godotの仕様上の問題だったのですね、解決方法等についても提示して下さり、本当にありがとうございます! issueの件についても了解いたしました。可能であれば、issueを投稿した際はこちらに関連付けて頂けると嬉しいです。(定期的に確認したいため)

and Thank you so much @Haledire !

creeper-0910 avatar Oct 06 '24 10:10 creeper-0910

macOS版の実装を参考にして最新の4.4.rcで実装を試みてみたのですが、結論から言いますと期待する動作になりませんでした。

手法としては、WM_NCHITTESTイベント内でmouse_passthrough_polygonプロパティに渡されたポリゴンに対してマウス位置のチェックを行い、ポリゴン範囲内であればHTCLIENTを、そうでない場合はHTTRANSPARENTを戻すという方法です。

この方法は、

  • 指定した領域のみマウスイベントに反応する
  • 描画領域のみがくり抜かれてしまうのを防ぐ

というのは満たせますが、以下を満たすことが出来ません。

  • 指定した領域外の下にあるアプリを操作(透過しているようにみせる)する

調べてみたところ、HTTRANSPARENTは、WS_EX_TRANSPARENTの様にアプリケーション間で透過ではなく、自分のアプリ(スレッド)内でのみ透過という動作になっているためでした。

それではと、WS_EX_TRANSPARENTを有効にしてみると、今度はマウスイベント全てを受け取らなくなってしまうため、アプリ全体を操作できなくなってしまいます。

さすがにこれでは改善提案としてGodot側にIssueを投稿するわけにもいきません。

一応、将来誰かが修正を試みる参考になるかもしれませんので、本家からブランチした自分のGitHubにアップロードしておきました。

https://github.com/MizunagiKB/godot/tree/correct_mouse_pass_through

返信にだいぶ時間がかかってしまいましたが、自分のアプローチとしては失敗で、creeper-0910さんの希望する結果にはなりませんでした。

かなり時間を経過してしまいましたが、結果をだせず大変申し訳ありませんでした。


I tried to implement a similar method to that used in macOS with the latest 4.4.rc, but unfortunately, it did not achieve the desired results.

The approach I attempted involved checking the mouse position against the polygon passed to the mouse_passthrough_polygon property within the WM_NCHITTEST event. If the mouse position was within the polygon, I returned HTCLIENT; otherwise, I returned HTTRANSPARENT.

This method fulfills the following:

  • Responds to mouse events only within the specified region
  • Prevents the drawing area from being cut out

However, it does not fulfill the following:

  • Allowing interaction with applications beneath the specified region (making it appear transparent)

Upon further investigation, I found that HTTRANSPARENT behaves like WS_EX_TRANSPARENT in that it only allows transparency within the application (thread) itself, not across applications.

When WS_EX_TRANSPARENT is enabled, the application itself does not receive mouse events, rendering the entire application non-operational.

Given these limitations, I cannot propose this as an improvement to Godot.

Although this may not be what you were hoping for, I have uploaded my implementation to my GitHub, which branched from the main repository. It may serve as a reference for anyone attempting to address this issue in the future.

https://github.com/MizunagiKB/godot/tree/correct_mouse_pass_through

I apologize for the delayed response. Unfortunately, my approach did not yield the desired results for creeper-0910.

I am truly sorry for not achieving the expected outcome after such a long time.

MizunagiKB avatar Feb 24 '25 04:02 MizunagiKB

貴重なお時間を割いて検証・実装していただき本当にありがとうございます🙏 現状、godot側の実装の解決できない問題(Windowsの問題?)の様ですので、いつか解決することを祈りつつこちらのissueは閉じさせて頂きます。 改めて、本当にありがとうございました!

creeper-0910 avatar Feb 24 '25 05:02 creeper-0910