HomeArchiveFeedShelf

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

https://chat.openai.com

@2023-11-25 14:47