Godotにおける動的3D物理ロープ - パート2
前回の記事では、2つのPhysicsBody3Dとロープの長さを考慮して、適切な場所にPinJoint3Dを動的に生成する問題を解決しました。
この記事では、グローバル空間においてこれらのPinJoint3Dに沿った3Dロープメッシュを描画することを試みます。
ImmediateMeshとCurve3D
ImmediateMeshは、ロープが物理フレームごとに常に更新されるため、非常に適しています。ImmediateMeshは、メッシュを描画するためのOpenGL 1.xスタイルの即時モードAPIを提供します。
Godotはまた、名前が示す通りのCurve3Dというクラスを提供しています。これら2つのクラスを組み合わせることで、次のステップでロープを描画できます。
- ロープ生成中にすべての
PinJoint3Dが適切な場所に配置された後、それらの位置と2つのアンカー位置をCurve3Dに保存します。 - 物理フレームごとに、ピンジョイントとアンカーの位置に基づいて曲線のポイントを更新します。これらの位置は、ゲームエンジンの物理によってすでに解決されています。
- レンダリングフレームごとに、曲線のポイントを使用して
ImmediateMeshで3Dロープを描画します。
Curve3Dから3Dロープを描画
曲線の各ポイントから円を押し出します。これはパラメトリック方程式における正確な円ではなく、リアルタイムレンダリングを行っているため、離散的な頂点で構成された近似的なものです。
以下の関数は、曲線のポイントから断面円のポイントを返します。section_circle_resolutionが増加するにつれて、円はより連続的になりますが、パフォーマンスのコストがかかります。
func _generate_cross_section_circle_points(point: Vector3, next_point: Vector3) -> PackedVector3Array:
var points := PackedVector3Array()
var dir := next_point - point
var right := dir.cross(Vector3.UP).normalized()
var up := right.cross(dir).normalized()
for i in section_circle_resolution:
var phi = 2.0 * PI * float(i) / float(section_circle_resolution)
var local_point := Vector3(sin(phi) * radius, cos(phi) * radius, 0.0)
var global_point := point + right * local_point.x + up * local_point.y
points.push_back(global_point)
return points
円のポイントを取得した後、兄弟円の間に四角形を描画します。四角形は単に2つの三角形です。頂点をImmediateMeshに時計回りに渡すことに注意してください。( a[i], b[i], b[j] )の後に( a[i], b[j], a[j] )です。

func _draw_quad_between_circle_points(circle_points: PackedVector3Array, next_circle_points: PackedVector3Array):
for i in section_circle_resolution:
var j := (i + 1) % section_circle_resolution
# 最初の三角形
var i_normal := -1.0 * (circle_points[i] - circle_points[j]).cross(next_circle_points[i] - circle_points[i]).normalized()
mesh.surface_set_normal(i_normal)
mesh.surface_add_vertex(circle_points[i])
var next_i_normal := -1.0 * (circle_points[i] - next_circle_points[i]).cross(next_circle_points[i] - next_circle_points[j]).normalized()
mesh.surface_set_normal(next_i_normal)
mesh.surface_add_vertex(next_circle_points[i])
var next_j_normal := -1.0 * (circle_points[j] - next_circle_points[j]).cross(next_circle_points[i] - next_circle_points[j]).normalized()
mesh.surface_set_normal(next_j_normal)
mesh.surface_add_vertex(next_circle_points[j])
# 2番目の三角形
mesh.surface_set_normal(i_normal)
mesh.surface_add_vertex(circle_points[i])
mesh.surface_set_normal(next_j_normal)
mesh.surface_add_vertex(next_circle_points[j])
var j_normal := -1.0 * (circle_points[i] - circle_points[j]).cross(next_circle_points[j] - circle_points[j]).normalized()
mesh.surface_set_normal(j_normal)
mesh.surface_add_vertex(circle_points[j])
詳細については、githubリポジトリをチェックしてください。特にソースファイルrope_mesh.gdを参照してください。
さらなる改善
リポジトリには、UVやRustのようなネイティブ言語での書き換えなど、まだ多くの作業があります。ピンジョイントが多ければ多いほど、よりリアルに感じ、動作します。
参考
https://docs.godotengine.org/en/stable/tutorials/3d/procedural_geometry/immediatemesh.html
https://docs.godotengine.org/en/stable/classes/class_curve3d.html