Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
creeper-0910 opened this issue Oct 16, 2023 · 15 comments

Comments

@creeper-0910
Copy link
Contributor

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

@MizunagiKB
Copy link
Owner

MizunagiKB commented Oct 16, 2023

@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の場合ですと、毎フレーム頂点を作り直しますので非力な環境だと辛いかもしれません。

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

@creeper-0910
Copy link
Contributor Author

creeper-0910 commented Oct 16, 2023

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

@MizunagiKB
Copy link
Owner

MizunagiKB commented Oct 16, 2023

@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
Copy link
Owner

MizunagiKB commented Oct 16, 2023

@creeper-0910

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

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

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

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

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

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

fig2

凸包について

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

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

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
Copy link
Owner

MizunagiKB commented Oct 19, 2023

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

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

@creeper-0910
Copy link
Contributor Author

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

@MizunagiKB
Copy link
Owner

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

@creeper-0910
Copy link
Contributor Author

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

@MizunagiKB
Copy link
Owner

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

@MizunagiKB MizunagiKB reopened this Sep 24, 2024
@MizunagiKB
Copy link
Owner

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

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

ss

@Haledire
Copy link

Haledire commented Sep 25, 2024

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:
godotengine/godot#76167 (comment)

@MizunagiKB
Copy link
Owner

@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
Copy link
Owner

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
Copy link
Owner

@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.

@creeper-0910
Copy link
Contributor Author

creeper-0910 commented Oct 6, 2024

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

and Thank you so much @Haledire !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants