Godot4.0　Path3DとPathFollow3Dを利用して曲線の道をメッシュで生成する

:::note
Godot4.0のメッシュ生成では三角形の頂点を反時計周りに配置した面が表でしたが、バグだったようで、4.1rc1を使用すると時計周りに配置した面が表になりました。
:::

https://github.com/footinglow/Godot4/tree/main/03_study/CreatePath3DMesh

# 目的
　Path3DとPathFollow3Dを利用して曲線の道をメッシュで生成します。
![スクリーンショット (770).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/237b0120-12bb-2e5b-9760-81f1f933ebb9.png)

# Path3D/PathFollow3D

>GODOT DOCS : Beziers, curves and paths
>https://docs.godotengine.org/en/latest/tutorials/math/beziers_and_curves.html#


# 確認用のシーンを作成する

| ノード名:ノードクラス名 |インスペクタ変更値|
|:-|:-|
|World : Node3D||
|　┣━Camera3D|Position : (0m, 5m, 0m)  Rotation : (-90°, 0°, 0°)
|　┣━DirectionalLight3D|Position : (-10m, 0m, 0m)  Rotation : (-90°, 0°, 0°)|
|　┣━MeshInstance3D||　　
|　┗┳Path3D|※設定は後述|
|　　┗┳PathFollow3D||
|　　　┣━left : Marker3D|Position : (-0.5m, 0m, 0m)|
|　　　┗━right : Marker3D|Position : (0.5m, 0m, 0m)|

　シーンは下記のようになります。（スクリプトをアタッチした後です）
![スクリーンショット (756).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/4e349128-fbe1-2df6-f37f-7e979ea123b0.png)


※Path3Dを選択した状態で、インスペクタのPath3D/Curve/Curve3Dをクリックして詳細設定を表示します。
　Pointsを開いて「＋要素を追加」ボタンを2回押下し、Pointsを下記のように設定します。
　Points[0] : Position(-2m, 0m,  1m), Out:(0m, 0m,  5m)
　Points[1] : Position( 2m, 0m, -1m), In :(0m, 0m, -5m)
![スクリーンショット (757).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/6666f7f1-6260-454c-7702-3d773ec509fe.png)

　３Dビューで上面図を選択すると下記のような表示になります。
　青い細かい矢印の線がPath3Dの曲線です。
![スクリーンショット (758).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/d10678c8-2c16-855e-3abb-170142837591.png)

# PathFollow3Dと子ノードのMarker3Dのleftとright
　Path3Dの子に配置したPathFollow3DのインスペクタにはProgress Ratioがあります。Progress Ratioを0から1まで変化すると、PathFollow3Dの位置がPATH3Dの曲線上を移動し向きも変化します。Progress Ratioを0に設定した時はPath3Dの曲線の開始点とその時の向き、Progress Ratioに1を設定したときはPath3Dの曲線の終了位置とその時の向きにPathFollow3Dノードが移動します。
　PathFollow3Dの子のleftノードとrightノードも追従して位置、向きがかわります。
　left.transform.origin.x=-0.5m、right.transform.origin.x=0.5mに設定して、Progress Ratioを0.1刻みで動かした時の位置を、leftを赤、rightを青の点で表したのが下図になります。
![スクリーンショット (766).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/e31576a1-fce8-c26a-f04d-a35ef050c2fa.png)
　Progress Ratioが0の時の赤と青の点と、Progress Ratioｇ0.1の時の赤と青の点を使って、四角形を作ることができます。同じように下図のように四角形をつくることができます。四角形は三角形2つで作ることができるので、ArrayMeshで作れそうです。
![スクリーンショット (765).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/197c9e48-9e30-32cc-1a78-c0a8d1be6865.png)

# スクリプトを実装する
　シーン内のルートノードのWorldを右クリックして、スクリプトのアタッチを実行して、下記のように変更します。

```GDScript
extends Node3D

@export var m_d_path_width  : float = 0.5	# 道の幅
@export var m_d_path_devide : float = 10.0	# 道を四角形に分割する数。大きいほど道がなめらかになります。

var m_v3_path_lefts = []
var m_v3_path_rights = []

func _ready():
	create_path($Path3D/PathFollow3D)
	create_path_mesh($MeshInstance3D)

func create_path(node_pf : PathFollow3D):
	# 道幅を設定
	var node_left : Marker3D = node_pf.get_node("left")
	var node_right : Marker3D = node_pf.get_node("right")
	node_left.transform.origin.x = -m_d_path_width / 2.0
	node_right.transform.origin.x = m_d_path_width / 2.0
	
	# マーカー追加する
	for i in range(m_d_path_devide+1):
		node_pf.progress_ratio = i /m_d_path_devide 

		var l_pos = node_pf.transform * node_left.position
		m_v3_path_lefts.append(l_pos)
		var r_pos = node_pf.transform * node_right.position
		m_v3_path_rights.append(r_pos)
	

func create_path_mesh(node_mesh : MeshInstance3D):
	var vertices = PackedVector3Array()
	var normals  = PackedVector3Array()

	var pre_v3_l = Vector3.ZERO
	var pre_v3_r = Vector3.ZERO
	var f_is_first = true
	for i in range(m_v3_path_lefts.size()):
		var v3_l = m_v3_path_lefts[i]
		var v3_r = m_v3_path_rights[i]
		if f_is_first :
			f_is_first = false
		else:
			vertices.push_back(v3_l)
			vertices.push_back(v3_r)
			vertices.push_back(pre_v3_r)
			normals.push_back(Vector3.UP)
			normals.push_back(Vector3.UP)
			normals.push_back(Vector3.UP)

			vertices.push_back(pre_v3_r)
			vertices.push_back(pre_v3_l)
			vertices.push_back(v3_l)
			normals.push_back(Vector3.UP)
			normals.push_back(Vector3.UP)
			normals.push_back(Vector3.UP)

		pre_v3_l = v3_l
		pre_v3_r = v3_r
	
	var arrays = Array()
	arrays.resize(ArrayMesh.ARRAY_MAX)
	arrays[ArrayMesh.ARRAY_VERTEX] = vertices
	arrays[ArrayMesh.ARRAY_NORMAL] = normals

	var tmpMesh = ArrayMesh.new()
	tmpMesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
	node_mesh.mesh= tmpMesh
```

　実行します。
![スクリーンショット (767).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/7e7cdf8c-aa69-44ba-9559-fba7a997749b.png)

m_d_path_devide=100.0に変更するともっとなめらかになります。
![スクリーンショット (768).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/15c62b77-6fb0-acb6-7116-0c9e1affeb0f.png)


# ソースコード説明

```GDScript
@export var m_d_path_width  : float = 0.5	# 道の幅
@export var m_d_path_devide : float = 10.0	# 道を四角形に分割する数。大きいほど道がなめらかになります。
```
　道の幅m_d_path_widthとパスの分割数m_d_path_devideを設定します。
　パスの分割数m_d_path_devideを増やすほどカーブがなめらかになります。
```GDScript
var m_v3_path_lefts = []
var m_v3_path_rights = []
```
　Path3Dの左側の赤い点と右側の青い点の座標をVector3の配列として保持します。
　m_v3_path_lefts配列、m_v3_path_rights配列を設定した後、ArrayMeshを作成します。

```GDScript
func _ready():
	create_path($Path3D/PathFollow3D)
	create_path_mesh($MeshInstance3D)
```
　SceneTreeに追加されたときにメッシュを作成します。
　引数にノードを渡すことで汎用的に使えるようになります。

```GDScript
func create_path(node_pf : PathFollow3D):
	# 道幅を設定
	var node_left : Marker3D = node_pf.get_node("left")
	var node_right : Marker3D = node_pf.get_node("right")
```
　$Path3D/PathFollow3D/leftとrightのノードを取得します。

```GDScript
	node_left.transform.origin.x = -m_d_path_width / 2.0
	node_right.transform.origin.x = m_d_path_width / 2.0
```
　道の幅を設定しています。
```GDScript
	# マーカー追加する
	for i in range(m_d_path_devide+1):
		node_pf.progress_ratio = i /m_d_path_devide 

		var l_pos = node_pf.transform * node_left.position
		m_v3_path_lefts.append(l_pos)
		var r_pos = node_pf.transform * node_right.position
		m_v3_path_rights.append(r_pos)
```
　PathFollow3Dのprogress_ratioを０～１まで変化させるためのfor分です。
　m_d_path_devideはfloatですが、range(m_d_path_devide)は整数の配列になります。
　range(m_d_path_devide)では分割数が1個足りないのでrange(m_d_path_devide+1)と＋１しています。
　(10の場合、0~9までループしますが、0と1で1個目、1と2で2個目...8と9で9個目の四角形を作ります）
　「i/m_d_path_devide」で0~1になります。

　node_left.positionはローカル座標系なので、親のPathFollow3Dの座標系に変換するために、親のPathFollow3Dのtransform(Transform3D)を使用して「node_pf.transform * node_left.position」としています。
　詳細は下記の記事を参考にしてください。

https://qiita.com/FootInGlow/items/f046fdd2f1596e32a190



```GDScript
func create_path_mesh(node_mesh : MeshInstance3D):
	var vertices = PackedVector3Array()
	var normals  = PackedVector3Array()

	var pre_v3_l = Vector3.ZERO
	var pre_v3_r = Vector3.ZERO
	var f_is_first = true
	for i in range(m_v3_path_lefts.size()):
		var v3_l = m_v3_path_lefts[i]
		var v3_r = m_v3_path_rights[i]
		if f_is_first :
			f_is_first = false
		else:
			vertices.push_back(v3_l)
			vertices.push_back(v3_r)
			vertices.push_back(pre_v3_r)
			normals.push_back(Vector3.UP)
			normals.push_back(Vector3.UP)
			normals.push_back(Vector3.UP)

			vertices.push_back(pre_v3_r)
			vertices.push_back(pre_v3_l)
			vertices.push_back(v3_l)
			normals.push_back(Vector3.UP)
			normals.push_back(Vector3.UP)
			normals.push_back(Vector3.UP)

		pre_v3_l = v3_l
		pre_v3_r = v3_r
	
	var arrays = Array()
	arrays.resize(ArrayMesh.ARRAY_MAX)
	arrays[ArrayMesh.ARRAY_VERTEX] = vertices
	arrays[ArrayMesh.ARRAY_NORMAL] = normals

	var tmpMesh = ArrayMesh.new()
	tmpMesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
	node_mesh.mesh= tmpMesh
```
　forループでひとつ前の左右位置を使って初めて四角形を作ることができるので、f_is_firstフラグをつくって0番目はスキップしています。ひとつ前のleft,right位置はpre_v3_l、pre_v3_rに格納されているので、pre_v3_l とpre_v3_rとv3_l とv3_rの4点を使って三角形を2つ作り、四角形にしています。
　vertices.push_backは時計回りになるように追加した面が、見えるようになるため、vertices.push_backする順番は注意する必要があります。
　ArrayMeshの作り方は下記を参考にしていただければと思います。

https://qiita.com/FootInGlow/items/54a1cccc8e12f44ba707

https://qiita.com/FootInGlow/items/2b94a3444fb5bb3c94f4

https://qiita.com/FootInGlow/items/a342823a16921a92d2c8


# github

https://github.com/footinglow/Godot4/tree/main/03_study/CreatePath3DMesh

# 3Dビューで表示確認
　create_path_meshメソッドの最後に下記の1行を追加しtmpMeshを保存して、MeshInstance3Dのmeshにドラッグすると生成したメッシュを３Dビューで確認することができます。
　また上面図を選択するリストで「ワイヤーフレーム」を選択すると三角形のメッシュの形を確認することができます。
```GDScript
	ResourceSaver.save(tmpMesh, "res://debug_mesh_path.tres", ResourceSaver.FLAG_COMPRESS)
```
![スクリーンショット (771).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/99a75ee4-89f1-57ca-d24f-82af2c3c6b57.png)



以上