MizunagiKB / gd_cubism

Unofficial Live2D Player for Godot Engine
https://mizunagikb.github.io/gd_cubism/
Other
111 stars 17 forks source link

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

Closed creeper-0910 closed 6 months ago

creeper-0910 commented 8 months ago

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

MizunagiKB commented 8 months ago

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

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

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

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

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

hitarea

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

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

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

creeper-0910 commented 8 months ago

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

MizunagiKB commented 8 months ago

@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 commented 8 months ago

@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 commented 8 months ago

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

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

creeper-0910 commented 8 months ago

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

MizunagiKB commented 6 months ago

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