Godot Engineでメッシュに関するいくつかのTips

# はじめに

今回は比較的単発なテクニックを試したのでそれを公開したい。

# UnityとGodotの違い

Tipsを紹介する前に、今回のTipsに関わるUnityとGodotでの決定的な違いについて、簡単に触れておきたい。

## 3Dモデル（オブジェクト）の表示・非表示に関するレイヤーの場所が違う

UnityでVRMを読み込むと次のようなヒエラルキー構造になる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/3da59324-102d-762d-6271-6babed426fbd.png)

UnityではGameObjectがレイヤーを持っており、どの位置でもレイヤーを切り替えて好きな目的で使用することができる。

一方、GodotでVRMを読み込むと次のようになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/83a3efa0-1817-24e6-e4b3-d86a288361d2.png)

たとえばVRM自身のノードには、プロパティのどこにもレイヤーはない。そもそもUnityとGodotでは基本的な構造が違うから仕方ない。

単に表示・非表示するだけならshow()とかそういう感じのメソッドを使えばいいだけだが、レイヤー分けして管理したり、優先度をつけて表示するときには困りそうだ。


## GodotではMeshInstance3Dのレイヤーを使う

MeshInstance3DはVisualInstance3Dを継承しており、そのクラスには `layers` プロパティがある。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/4cb92d6c-31f9-cd9f-c060-049d50a4c7ab.png)

この `layers` プロパティはプロジェクトの設定からもレイヤー名を変えられるようになっている。どうやらこれがUnityのGameObjectのlayerに相当するものと考えてよさそうだ。

CSGPrimitive3Dを継承する基本的な図形も、当然このVisualInstance3Dを継承しているので `layers` は存在するのがわかる。

GodotのノードというのはUnityのGameObject（とComponent）と違って、一つのノードに複数の機能をもたせる考え方ではないようだ。
つまり、機能単位でノード・子ノードの構成を作る必要がある。

* 3Dモデルの表示に関わるノード
* 3Dモデルのアニメーションの再生に関わるノード
* 3Dモデルの衝突に関するノード

逆に言うと、特定の操作をするなら取得すべきノードが自ずと決まってくるので、わかりやすいかもしれない。

:::note warn
VisualInstance3Dを継承するクラスでしか通用しない点に注意。
ノードを追加する際のウィンドウでNode3D - VisualInstance3D のツリーを開けばどのクラスがこの `layers` を使えるのかざっと確認できるだろう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/c995417c-c3fb-fdf2-65cf-ecb1f04b78af.png)

:::

# Tips

さて、違いを踏まえた上で、今回の記事のメインに進む。

## 3Dモデルのメッシュのレイヤーを一度に変える

前述の通り、Unityならば親のGameObjectのレイヤーを切り替えるだけで済んだが、Godotではそうも行かない。

**VisualInstance3Dを継承するクラスを型に持つノード全部の `layers` を切り替える**

```gdscript:test.gd
func set_layer_value(layer:int):
    return 1 << (layer - 1)

func recur_set_layer(layer: int, targets: Node):
	var children = targets.get_children(true)
	for child in children:
		if child is MeshInstance3D:
			(child as MeshInstance3D).layers = set_layer_value(3) #設定したいレイヤー番号
		recur_set_layer(layer, child)
```

再帰処理でMeshInstance3Dを型に持つノードに対して `layers` を設定してあげればいい。

これで例えばVRMを特定のレイヤーに表示して、別のレイヤーの3Dモデルと重ねて表示などができるようになる。

ただメッシュの部位によってレイヤーを変えたいとかそういう追加機能をつけたい場合、渡す引数を工夫する必要がある。

## 3Dモデルの全体の大きさを取得する

ファイルサイズではなく、3Dモデルを構成するメッシュの端から端までのつまりBoundingのサイズのことである。
このテクニックはなにもGodotだけではなく、Unityでもよくあるものだろう。

公式マニュアルでは次のページだ。

[MeshInstance3D](https://docs.godotengine.org/ja/4.x/classes/class_meshinstance3d.html)
そしてGodotではBoundingなクラスがAABBだ。
[AABB](https://docs.godotengine.org/ja/4.x/classes/class_aabb.html)

ぶっちゃけ、Claude先生に教わって作ったので、AABBとかギャグかなと思う名前のクラスがあることにも驚いた。

```gdscript:test.gd
func get_total_aabb(targets) -> AABB:
	var total_aabb = AABB()
	var first = true
	
	# 全てのMeshInstance3Dを再帰的に検索
	var allmeshes = get_all_mesh_instances(targets)
	for child: MeshInstance3D in allmeshes:
		var local_transform = child.get_global_transform()
		var mesh_aabb = child.get_aabb()
		
		# メッシュのAABBをグローバル座標系に変換
		var global_aabb = local_transform * mesh_aabb

		if first:
			total_aabb = global_aabb
			first = false
		else:
			# AABBを結合
			total_aabb = total_aabb.merge(global_aabb)
	
	return total_aabb

func get_all_mesh_instances(node: Node) -> Array:
    """
    子・孫階層すべてのMeshInstance3Dをひとまとめに取得する
    """
	var mesh_instances = []

	if node is MeshInstance3D:
		mesh_instances.append(node)

	for child in node.get_children():
		mesh_instances.append_array(get_all_mesh_instances(child))

	return mesh_instances

func any_function():
    ...
    get_total_aabb(vrm)
    ...
```

`get_total_aabb` がメインのメソッドである。
その中では受け渡された最初のノードを起点とし、 `get_all_mesh_instances` メソッドで再帰処理にてMeshInstance3Dを探し出し、配列にしている。

そしてMeshInstance3Dの配列に対して `get_aabb()` メソッドでAABB型のデータを取得し、Transformを使ってグローバル座標のAABBに変換し、それをひたすら `merge()` メソッドで結合していく。

### printで出力して確認

AABBにはpositionとsizeがある。Sがおそらくsizeだろう。
出力された値と、Unityで作った現行のVRMViewMeisterを使い、身長として表示した値を比べてみた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38416/61050609-97a4-af8d-bb50-a1af74ff87ed.png)

positionを考慮する必要があるのかもしれない。

```
1.77005 - 0.13554 = 1.634496
```

sizeをpositionで減算したところ大体同じ値となった。Unityで算出した値とGodotの今回のテクニックで取得した値、どうやら問題なさそうだ。
（若干の誤差は気にしないでおく）


# おわりに

自分にとって必要なテクニックを発見したので、覚え書きとして今回の記事にまとめた次第である。
この記事が皆様のお役に立てれば幸いだ。