GodotのArrayMeshの法線の向きとライティングを確認する。またArrayMeshを保存してビューアで確認する

# やりたいこと
　GODOT4.0のArrayMeshの法線について確認しました。
　またArrayMeshを保存してビューアで確認します。

# GITHUB
　本記事を最後まで実行したプロジェクトです。

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


# 新規シーンを作成して必要なノードを追加
　Godotで新規プロジェクトを作成して、下記のようにノードを構成します。

World : Node3D
　┝━Camera3D
　┝━DirectionalLight3D
　┗━ArrayMesh : MeshInstance3D

* **Camera3D**
地面を見下ろすように設定します。
Node3D/Transform/Position : (x,y,z)=(0m, 4m, 7m)
Node3D/Transform/Rotation : (x,y,z)=(-45°, 0°, 0°)
* **DirectionalLight3D**
左斜め前方に影が伸びるように設定します。
Light3D/Light/Shadow/Enable : オン
Node3D/Transform/Position : (x,y,z)=(-10m, 0m, 0m)　※DirectionalLight3Dのpositionはどこでもよい
Node3D/Transform/Rotation : (x,y,z)=(-10°, 45°, 0°)

# 三角形を組み合わせて、半径と高さが３ｍの六角錐を作る
　三角形を6個組み合わせて下図のような六角錐をつくります。半径も高さも３ｍにします。
![スクリーンショット (640).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/1d1e9035-4cfe-9af7-0917-6655f6e72de0.png)
　a,b,cの点は下図左のように計算します。2つ目以降は下図右のようにします。3番目以降も順番にずれていきます。
　注意点として、三角形の頂点3点を配列に格納する時には時計回りになるように格納する必要があります。そのため点a,c,bの順番に配列に格納します。
![スクリーンショット (650).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/854cdd78-fe05-7f56-4257-875eb3b63358.png)

# スクリプトの実装
　ルートノードのWorldを右クリックして、「スクリプトにアタッチ」して下記のようにスクリプトを修正します。
　最後の「ResourceSaver.save()」メソッドでメッシュをリソースファイルとして保存することができます。

```GDScript
extends Node3D

var m_i_radial_segments_num = 6		# 六角錐の６
var m_d_radius_m = 3.0				# 半径
var m_d_height_m = 3.0				# 高さ

func _ready():
	var vertices = PackedVector3Array()

	# 1周分の角度をラジアンでリストに保存する。最後にスタート地点の0°を追加する
	var d_rad_list = Array()
	for i in range(m_i_radial_segments_num):
		var rate = float(i) / float(m_i_radial_segments_num)
		d_rad_list.push_back( 2.0 * PI * rate )
	d_rad_list.push_back(0.0)
	
	# 三角形を追加する
	for i in range(m_i_radial_segments_num):
		var a = Vector3(0, 0, m_d_height_m)
		var b = Vector3(cos(d_rad_list[i  ]) * m_d_radius_m, sin(d_rad_list[i  ]) * m_d_radius_m, 0)
		var c = Vector3(cos(d_rad_list[i+1]) * m_d_radius_m, sin(d_rad_list[i+1]) * m_d_radius_m, 0)
        # 配列には頂点a,c,bの順番に格納する
		vertices.push_back(a)
		vertices.push_back(c)
		vertices.push_back(b)

	var arrays = Array()
	arrays.resize(ArrayMesh.ARRAY_MAX)
	arrays[ArrayMesh.ARRAY_VERTEX] = vertices

	var tmpMesh = ArrayMesh.new()
	tmpMesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
	$ArrayMesh.mesh = tmpMesh
	
	# メッシュをファイルに保存する
	ResourceSaver.save(tmpMesh, "res://hexagonal_pyramid.tres", ResourceSaver.FLAG_COMPRESS)
```

　実行します。法線を設定していないため、真っ暗です。
![スクリーンショット (642).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/5b0913c8-6c1e-a25e-9124-434fdd3f5a1f.png)

　メッシュが「res://hexagonal_pyramid.tres」リソースファイルとして保存されました。
![スクリーンショット (645).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/066028b1-f991-5dc0-2cc7-64a302348952.png)

# 保存されたメッシュをビューアで確認する
　ルートノードのWorldを右クリックして子ノードを追加からMeshInstance3Dを選択します。
　名称を「SavedMesh」に変更します。
　ファイルシステム内のres://hexagonal_pyramid.tresをインスペクタのMeshInstance3D/Meshにドラッグアンドドロップします。
![スクリーンショット (646).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/380f780f-9ad3-41a0-87d9-65590d264b12.png)
　メッシュが登録されました。
![スクリーンショット (647).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/c085bc23-4d92-5a22-492e-e814fff871f3.png)
　上部３Dをクリックするとビューアで確認できます。
　真っ暗ですが、回転してみると六角錐のような形であることが確認できます。
![スクリーンショット (649).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/15eda56f-3ced-0d44-4079-eaa90bc24043.png)

# 法線を正しく設定する。
　面に垂直になるように、三角形の3点それぞれに法線を設定します。
　外積(Cross product)を使うと簡単に法線(Normal)を得ることができます。

```GDScript
		var ab = b - a
		var ac = c - a
		var normal = ab.cross(ac)
```

>参考　外積：Cross product
>https://docs.godotengine.org/en/stable/tutorials/math/vector_math.html#cross-product
 
　スクリプト全体は下記になります。

```GDScript
extends Node3D

var m_i_radial_segments_num = 6		# 六角錐の６
var m_d_radius_m = 3.0				# 半径
var m_d_height_m = 3.0				# 高さ

func _ready():
	var vertices = PackedVector3Array()
	var normals = PackedVector3Array()

	# 1周分の角度をアジアンでリストに保存する。最後にスタート地点の0°を追加する
	var d_rad_list = Array()
	for i in range(m_i_radial_segments_num):
		var rate = float(i) / float(m_i_radial_segments_num)
		d_rad_list.push_back( 2.0 * PI * rate )
	d_rad_list.push_back(0.0)
	
	# 三角形を追加する
	for i in range(m_i_radial_segments_num):
		var a = Vector3(0, 0, m_d_height_m)
		var b = Vector3(cos(d_rad_list[i  ]) * m_d_radius_m, sin(d_rad_list[i  ]) * m_d_radius_m, 0)
		var c = Vector3(cos(d_rad_list[i+1]) * m_d_radius_m, sin(d_rad_list[i+1]) * m_d_radius_m, 0)
		# 配列には頂点a,c,bの順番に格納する
		vertices.push_back(a)
		vertices.push_back(c)
		vertices.push_back(b)

		#　外積で法線を求める
		var ab = b - a
		var ac = c - a
		var normal = ab.cross(ac)
		normals.push_back(normal)
		normals.push_back(normal)
		normals.push_back(normal)

	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)
	$ArrayMesh.mesh = tmpMesh
	
	# メッシュをファイルに保存する
	ResourceSaver.save(tmpMesh, "res://hexagonal_pyramid.tres", ResourceSaver.FLAG_COMPRESS)
```
　実行します。適切に陰影が付きました。

![スクリーンショット (652).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/a99054fa-5d42-eafb-b89f-e4650621fe37.png)

# 法線を円方向にする。
　六角錐は半径も高さも3mです。
　法線として原点(X,y,z)=(0,0,0)からの点の向きをそのまま設定してみます。

```GDScript
		# 法線を原点x,y,z)=0,0,0)から放射状に設定する
		normals.push_back(a.normalized())
		normals.push_back(c.normalized())
		normals.push_back(b.normalized())
```

```GDScript
extends Node3D

var m_i_radial_segments_num = 6		# 六角錐の６
var m_d_radius_m = 3.0				# 半径
var m_d_height_m = 3.0				# 高さ

func _ready():
	var vertices = PackedVector3Array()
	var normals = PackedVector3Array()

	# 1周分の角度をアジアンでリストに保存する。最後にスタート地点の0°を追加する
	var d_rad_list = Array()
	for i in range(m_i_radial_segments_num):
		var rate = float(i) / float(m_i_radial_segments_num)
		d_rad_list.push_back( 2.0 * PI * rate )
	d_rad_list.push_back(0.0)
	
	# 三角形を追加する
	for i in range(m_i_radial_segments_num):
		var a = Vector3(0, 0, m_d_height_m)
		var b = Vector3(cos(d_rad_list[i  ]) * m_d_radius_m, sin(d_rad_list[i  ]) * m_d_radius_m, 0)
		var c = Vector3(cos(d_rad_list[i+1]) * m_d_radius_m, sin(d_rad_list[i+1]) * m_d_radius_m, 0)
		# 配列には頂点a,c,bの順番に格納する
		vertices.push_back(a)
		vertices.push_back(c)
		vertices.push_back(b)

		# 法線を原点x,y,z)=0,0,0)から放射状に設定する
		normals.push_back(a.normalized())
		normals.push_back(c.normalized())
		normals.push_back(b.normalized())
		
	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)
	$ArrayMesh.mesh = tmpMesh
	
	# メッシュをファイルに保存する
	ResourceSaver.save(tmpMesh, "res://hexagonal_pyramid.tres", ResourceSaver.FLAG_COMPRESS)
```
　丸っこくなりました。
![スクリーンショット (653).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/52b9cf0e-6d33-ec69-37cc-917c76588b4d.png)
　ビューアで斜め横から見ると形はとがっています。
　円のような丸い形状は三角形の組み合わせでできています。本当は丸ではないのですが、法線を使用してライティングを変更することで丸く見えるように工夫しているのですね。
![スクリーンショット (654).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/51c7a3ea-74b7-0650-ed90-485fb56fe46a.png)

# テクスチャを設定する
　テクスチャを設定してみます。
　3D TEXTURESのtiles 043を使用しました。CC0ライセンスです。
　Googleで「3D TEXTURES tiles 043」で検索して、下の方の「DOWNLOAD ALL THE MAPS」をクリックするとGoogle Driveに飛んでダウンロードすることができます。

　「Tiles_043_basecolor.jpg」というファイル名です。
　Windowsのエクスプローラから、Godotのファイルシステムのres:にドラッグアンドドロップします。
![スクリーンショット (658).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/e693e386-09bf-a44a-098f-d2af0957304f.png)

　ArrayMeshにテクスチャを貼るためにはUVを設定する必要があります。
　六角形の半径は３ｍです。左上(x,y)=(-3,-3)の場合、(u,v)=(0,0)、右下(x,y)=(3,3)の場合、(u,v)=(1,1)になるように、頂点に対して(u,v)を設定します。右の図のようにテクスチャを貼ります。
![スクリーンショット (659).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/c35de935-1936-16cb-3a69-e0d72ef0b23b.png)

```GDScript
		# UV設定
		# 半径が3mの場合、(x,y)=(-3,-3)が0,0)、3,3)が1,1)になるように設定する
		var u = (-m_d_radius_m + a.x) / ( 2 * m_d_radius_m )
		var v = (-m_d_radius_m + a.y) / ( 2 * m_d_radius_m )
		uvs.append(Vector2(u, v))
		u = (-m_d_radius_m + c.x) / ( 2 * m_d_radius_m )
		v = (-m_d_radius_m + c.y) / ( 2 * m_d_radius_m )
		uvs.append(Vector2(u, v))
		u = (-m_d_radius_m + b.x) / ( 2 * m_d_radius_m )
		v = (-m_d_radius_m + b.y) / ( 2 * m_d_radius_m )
		uvs.append(Vector2(u, v))
```

　テクスチャを貼るスクリプト全体は下記になります。
```GDScript
extends Node3D

var m_i_radial_segments_num = 100		# 六角錐の６
var m_d_radius_m = 3.0				# 半径
var m_d_height_m = 3.0				# 高さ

var m_texture = preload("res://Tiles_043_basecolor.jpg")

func _ready():
	var vertices = PackedVector3Array()
	var normals = PackedVector3Array()
	var uvs = PackedVector2Array()

	# 1周分の角度をアジアンでリストに保存する。最後にスタート地点の0°を追加する
	var d_rad_list = Array()
	for i in range(m_i_radial_segments_num):
		var rate = float(i) / float(m_i_radial_segments_num)
		d_rad_list.push_back( 2.0 * PI * rate )
	d_rad_list.push_back(0.0)
	
	# 三角形を追加する
	for i in range(m_i_radial_segments_num):
		var a = Vector3(0, 0, m_d_height_m)
		var b = Vector3(cos(d_rad_list[i  ]) * m_d_radius_m, sin(d_rad_list[i  ]) * m_d_radius_m, 0)
		var c = Vector3(cos(d_rad_list[i+1]) * m_d_radius_m, sin(d_rad_list[i+1]) * m_d_radius_m, 0)
		# 配列には頂点a,c,bの順番に格納する
		vertices.push_back(a)
		vertices.push_back(c)
		vertices.push_back(b)

		# 法線を原点x,y,z)=0,0,0)から放射状に設定する
		normals.push_back(a.normalized())
		normals.push_back(c.normalized())
		normals.push_back(b.normalized())
		
		# UV設定
		# 半径が3mの場合、(x,y)=(-3,-3)が0,0)、3,3)が1,1)になるように設定する
		var u = (-m_d_radius_m + a.x) / ( 2 * m_d_radius_m )
		var v = (-m_d_radius_m + a.y) / ( 2 * m_d_radius_m )
		uvs.append(Vector2(u, v))
		u = (-m_d_radius_m + c.x) / ( 2 * m_d_radius_m )
		v = (-m_d_radius_m + c.y) / ( 2 * m_d_radius_m )
		uvs.append(Vector2(u, v))
		u = (-m_d_radius_m + b.x) / ( 2 * m_d_radius_m )
		v = (-m_d_radius_m + b.y) / ( 2 * m_d_radius_m )
		uvs.append(Vector2(u, v))

	var arrays = Array()
	arrays.resize(ArrayMesh.ARRAY_MAX)
	arrays[ArrayMesh.ARRAY_VERTEX] = vertices
	arrays[ArrayMesh.ARRAY_NORMAL] = normals
	arrays[ArrayMesh.ARRAY_TEX_UV] = uvs

	var tmpMesh = ArrayMesh.new()
	tmpMesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
	$ArrayMesh.mesh = tmpMesh
	
	# Materialを設定
	var material = StandardMaterial3D.new()
	material.albedo_texture = m_texture
	$ArrayMesh.mesh.surface_set_material(0, material)
	
	# メッシュをファイルに保存する
	ResourceSaver.save(tmpMesh, "res://hexagonal_pyramid.tres", ResourceSaver.FLAG_COMPRESS)
```

　実行画面です。
![スクリーンショット (660).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/c7da3b6a-2f34-fc8d-9b5e-c109602382ec.png)
　ビューアでも表示されました。
![スクリーンショット (661).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/d29b71d4-003c-fa3e-0be8-76ed14d3d313.png)

　以上