スプラインから3Dレーストラックを生成するにはどうすればよいですか?


9

形状を表すスプラインの周りに3次元のレーストラックを生成したい。 こちらがビデオの例です。

トラックは、いくつかの障害物が投げ込まれた3Dスプラインに沿ってスイープするエンドレストンネルである必要があります。私のこれまでの最良のアイデアは、スプラインに沿って円形状をロフトすることでした。

また、ジオメトリを管理する方法、つまりロフト操作からジオメトリを作成する方法、メモリ内の「ライフサイクル」を管理する方法、コリジョン、テクスチャリングへのポインタも必要です。


1
非常に多くのprounのようになります。proun-game.com。著作権の問題がない場合は、このゲームの作成者から適切な回答が得られる可能性があります。ここで彼の情報を取得できます:blog.oogst3d.net
Vite Falcon

ええ、私はそれについて考えていました-tnx!
Valentin Galea 2012年

回答:


5

使用している言語はわかりませんが、Unity3Dの手続き型メッシュ押し出しの例は次のとおりです。

http://unity3d.com/support/resources/example-projects/procedural-examples

コードを見て、状況に合わせて書き直すことができると思います。

編集:私はあなたが始めているもののような手続き型押し出しレールシステムを使用するゲームに取り組んでいますが、それはUnity3dのC#にあります。キュービックベジェパスに基づいてレール押し出しを作成する方法の概要を説明します。レールのメッシュは手続き的に生成されますが、それはエディタで事前に定義したベジェパスに基づいています。ゲームの場合はレベルエディターのように、私の場合はピンボールテーブルをデザインしています。以下は、私がそれをどのように行っているかの例です。

1.)Bezier Pathクラスを構築/検索して実装します。これにより、メッシュ押し出しのソースデータが得られます。C#には、C ++に移植できるものが1つあります。

http://forum.unity3d.com/threads/32954-Waypoints-and-constant-variable-speed-problems?p=213942

2.)ベジェパスを作成すると、このパスのデータポイントがサンプリングされます。これは、上記のクラスのInterpメソッドを介して行うことができます。これにより、ベジェパスに沿ったVector3ポイントのリスト/配列が得られます。

3.)ステップ2のVector3ベジェパスデータを変換するヘルパークラスを作成します。この場合、以下に定義するように、ExtrudedTrailSectionと呼ばれる単純なクラスがあります。

public class ExtrudedTrailSection
{
    public Vector3 point;
    public Matrix4x4 matrix;
    public float time;

    public ExtrudedTrailSection() { }
}

4.)Vector3サンプルデータを反復処理し、ExtrudedTrailSectionsの配列に変換して、サンプルデータと、押し出しメッシュのルート位置となるベースマトリックスを提供します。

  1. )ExtrudedTrailSectionsの配列を使用し、次のコードを使用して最終的なMatrix4x4 []の配列を作成します。

Matrix4x4 worldToLocal = rootTransform.worldToLocalMatrix;

    for (int i = 0; i < trailSections.Count; i++)
    {
            if (i == 0)
            {
                direction = trailSections[0].point - trailSections[1].point;
                rotation = Quaternion.LookRotation(direction, Vector3.up);
                previousRotation = rotation;
                finalSections[i] = worldToLocal * Matrix4x4.TRS(position, rotation, Vector3.one);
            }
            // all elements get the direction by looking up the next section
            else if (i != trailSections.Count - 1)
            {
                direction = trailSections[i].point - trailSections[i + 1].point;
                rotation = Quaternion.LookRotation(direction, Vector3.up);

                // When the angle of the rotation compared to the last segment is too high
                // smooth the rotation a little bit. Optimally we would smooth the entire sections array.
                if (Quaternion.Angle(previousRotation, rotation) > 20)
                    rotation = Quaternion.Slerp(previousRotation, rotation, 0.5f);

                previousRotation = rotation;
                finalSections[i] = worldToLocal * Matrix4x4.TRS(trailSections[i].point, rotation, Vector3.one);
            }
            // except the last one, which just copies the previous one
            else
            {
                finalSections[i] = finalSections[i - 1];
            }
        }

6.)これでMatrix4x4 []の配列ができ、メッシュを押し出すことができますが、最初に押し出す参照メッシュが必要です。メッシュ押し出しメソッドに提供する円形メッシュ面を作成するユーティリティクラスがあります。

public static List<Vector2> CreateCircle (double radius, int sides)
{
    List<Vector2> vectors = new List<Vector2> ();

    const float max = 2.0f * Mathf.PI;
    float step = max / sides;

    for (float theta = 0.0f; theta < max; theta += step) {
        vectors.Add (new Vector2 ((float)(radius * Mathf.Cos (theta)), (float)(radius * Mathf.Sin (theta))));
    }


    return vectors;
}

7.)このデータの中心を見つける:

    public static Vector2 CalculateCentroid(List<Vector2> vectorList)
    {
        //////////////////////////////////////////////////////////////////////////
        // Local variables.
        float fArea = 0.0f, fDistance = 0.0f;
        Vector2 vCenter = Vector2.zero;
        int nIndex = 0, nLastPointIndex = vectorList.Count - 1;
        //
        //////////////////////////////////////////////////////////////////////////

        //////////////////////////////////////////////////////////////////////////
        // Run through the list of positions.
        for (int i = 0; i <= nLastPointIndex; ++i)
        {
            //////////////////////////////////////////////////////////////////////////
            // Cacluate index.
            nIndex = (i + 1) % (nLastPointIndex + 1);

            // Calculate distance.
            fDistance = vectorList[i].x * vectorList[nIndex].y - vectorList[nIndex].x * vectorList[i].y;

            // Acculmate area.
            fArea += fDistance;

            // Move center positions based on positions and distance.
            vCenter.x += (vectorList[i].x + vectorList[nIndex].x) * fDistance;
            vCenter.y += (vectorList[i].y + vectorList[nIndex].y) * fDistance;
        }
        //
        //////////////////////////////////////////////////////////////////////////

        //////////////////////////////////////////////////////////////////////////
        // Calculate the final center position.
        fArea *= 0.5f;
        vCenter.x *= 1.0f / (6.0f * fArea);
        vCenter.y *= 1.0f / (6.0f * fArea);
        //
        //////////////////////////////////////////////////////////////////////////

        return vCenter;
    }

8.)これで、放射状面メッシュのエッジデータと中心データが得られたので、データを使用してメッシュオブジェクトを作成できます。メッシュの最後の頂点は、計算した中心点です。最終的なメッシュは、Unityパッケージの手続き型メッシュ押し出しクラスで例を提供したメッシュ押し出しメソッドに提供される面にすぎません。繰り返しますが、これは私の方法であり、明らかにこのデータをOpenGLにフィードする必要があります。使用している3Dユーティリティライブラリがある場合、または独自のメッシュクラスを作成できる場合、openglではレンダリングのためにこのデータは実際には必要ないため、最終的な押し出しメッシュを生成する方がおそらくうまくいきます。この面メッシュは、メッシュ押し出しの参照として使用されます。

    List<Vector3> levelVerts = new List<Vector3>();
    List<Vector2> levelUVBary = new List<Vector2>();
    List<Vector2> levelUVs = new List<Vector2>();
    List<int> levelTris = new List<int>();

    int verticesPerNode = 4;
    int edgeCount = sourceMeshData.Count;

    List<Vector3> sourceVerts = new List<Vector3>();
    //Debug.Log("smd.c:" + sourceMeshData.Count);
    for (int i = 0; i < edgeCount; i++)
    {
        //Debug.Log("adding:"+levelShapeData[i].x+"/"+levelShapeData[i].y);
        sourceVerts.Add(new Vector3(sourceMeshData[i].x, sourceMeshData[i].y, 0));
        levelUVs.Add(new Vector2(0, 0));
        //sourceVerts.Add(new Vector3(levelShapeData[i].x, levelShapeData[i].y, modelLength / 2f));
    }

    sourceVerts.Add(new Vector3(sourceMeshCenter.x, sourceMeshCenter.y, 0));
    levelUVs.Add(new Vector2(0, 0));

    for (int i = 0; i < edgeCount - 1; i++)
    {                                       //0, 1, 2, 3
        levelTris.Add(sourceVerts.Count - 1); //4, 4, 4, 4 
        levelTris.Add(i);                   //0, 1, 2, 
        levelTris.Add(i + 1);               //1, 2, 3,
    }

    levelTris.Add(sourceVerts.Count - 1);
    levelTris.Add(edgeCount - 1);
    levelTris.Add(0);

9.)メッシュ押し出し法で必要に応じて、円形メッシュの外側のエッジを見つけます。このコードも、Unityパッケージで提供されています。

public class Edge
{
    // The indiex to each vertex
    public int[]  vertexIndex = new int[2];
    // The index into the face.
    // (faceindex[0] == faceindex[1] means the edge connects to only one triangle)
    public int[]  faceIndex = new int[2];
}

public static Edge[] BuildManifoldEdges (Mesh mesh)
{
    // Build a edge list for all unique edges in the mesh
    Edge[] edges = BuildEdges(mesh.vertexCount, mesh.triangles);

    // We only want edges that connect to a single triangle
    ArrayList culledEdges = new ArrayList();
    foreach (Edge edge in edges)
    {
        if (edge.faceIndex[0] == edge.faceIndex[1])
        {
            culledEdges.Add(edge);
        }
    }

    return culledEdges.ToArray(typeof(Edge)) as Edge[];
}

10.)このすべてのデータをMesh Extrusionメソッドに入力します。

public static void ExtrudeMesh (Mesh srcMesh, Mesh extrudedMesh, Matrix4x4[] extrusion, Edge[] edges, bool invertFaces)
{
    int extrudedVertexCount = edges.Length * 2 * extrusion.Length;
    int triIndicesPerStep = edges.Length * 6;
    int extrudedTriIndexCount = triIndicesPerStep * (extrusion.Length -1);

    Vector3[] inputVertices = srcMesh.vertices;
    Vector2[] inputUV = srcMesh.uv;
    int[] inputTriangles = srcMesh.triangles;

    //Debug.Log("inputUV:" + inputUV.Length);

    Vector3[] vertices = new Vector3[extrudedVertexCount + srcMesh.vertexCount * 2];
    Vector2[] uvs = new Vector2[vertices.Length];
    int[] triangles = new int[extrudedTriIndexCount + inputTriangles.Length * 2];

    // Build extruded vertices
    int v = 0;
    for (int i=0;i<extrusion.Length;i++)
    {
        Matrix4x4 matrix = extrusion[i];
        float vcoord = (float)i / (extrusion.Length -1);
        foreach (Edge e in edges)
        {
            //Debug.Log(e.vertexIndex.Length);
            vertices[v+0] = matrix.MultiplyPoint(inputVertices[e.vertexIndex[0]]);
            vertices[v+1] = matrix.MultiplyPoint(inputVertices[e.vertexIndex[1]]);

            uvs[v+0] = new Vector2 (inputUV[e.vertexIndex[0]].x, vcoord);
            uvs[v+1] = new Vector2 (inputUV[e.vertexIndex[1]].x, vcoord);

            v += 2;
        }
    }       

    // Build cap vertices
    // * The bottom mesh we scale along it's negative extrusion direction. This way extruding a half sphere results in a capsule.
    for (int c=0;c<2;c++)
    {
        Matrix4x4 matrix = extrusion[c == 0 ? 0 : extrusion.Length-1];
        int firstCapVertex = c == 0 ? extrudedVertexCount : extrudedVertexCount + inputVertices.Length;
        for (int i=0;i<inputVertices.Length;i++)
        {
            vertices[firstCapVertex + i] = matrix.MultiplyPoint(inputVertices[i]);
            uvs[firstCapVertex + i] = inputUV[i];
        }
    }

    // Build extruded triangles
    for (int i=0;i<extrusion.Length-1;i++)
    {
        int baseVertexIndex = (edges.Length * 2) * i;
        int nextVertexIndex = (edges.Length * 2) * (i+1);
        for (int e=0;e<edges.Length;e++)
        {
            int triIndex = i * triIndicesPerStep + e * 6;

            triangles[triIndex + 0] = baseVertexIndex + e * 2;
            triangles[triIndex + 1] = nextVertexIndex  + e * 2;
            triangles[triIndex + 2] = baseVertexIndex + e * 2 + 1;
            triangles[triIndex + 3] = nextVertexIndex + e * 2;
            triangles[triIndex + 4] = nextVertexIndex + e * 2 + 1;
            triangles[triIndex + 5] = baseVertexIndex  + e * 2 + 1;
        }
    }

    // build cap triangles
    int triCount = inputTriangles.Length / 3;
    // Top
    {
        int firstCapVertex = extrudedVertexCount;
        int firstCapTriIndex = extrudedTriIndexCount;
        for (int i=0;i<triCount;i++)
        {
            triangles[i*3 + firstCapTriIndex + 0] = inputTriangles[i * 3 + 1] + firstCapVertex;
            triangles[i*3 + firstCapTriIndex + 1] = inputTriangles[i * 3 + 2] + firstCapVertex;
            triangles[i*3 + firstCapTriIndex + 2] = inputTriangles[i * 3 + 0] + firstCapVertex;
        }
    }

    // Bottom
    {
        int firstCapVertex = extrudedVertexCount + inputVertices.Length;
        int firstCapTriIndex = extrudedTriIndexCount + inputTriangles.Length;
        for (int i=0;i<triCount;i++)
        {
            triangles[i*3 + firstCapTriIndex + 0] = inputTriangles[i * 3 + 0] + firstCapVertex;
            triangles[i*3 + firstCapTriIndex + 1] = inputTriangles[i * 3 + 2] + firstCapVertex;
            triangles[i*3 + firstCapTriIndex + 2] = inputTriangles[i * 3 + 1] + firstCapVertex;
        }
    }

    if (invertFaces)
    {
        for (int i=0;i<triangles.Length/3;i++)
        {
            int temp = triangles[i*3 + 0];
            triangles[i*3 + 0] = triangles[i*3 + 1];
            triangles[i*3 + 1] = temp;
        }
    }

    extrudedMesh.vertices = vertices;
    extrudedMesh.uv = uvs;
    extrudedMesh.triangles = triangles;
}

私の場合の最終的な出力は次のようになります。

ここに画像の説明を入力してください

頑張って、あなたのゲームは本当にクールに見えます!わかりましたら教えてください。

チャック


私は主にOpenGLとC ++に興味がありますが、「疑似コード」も役に立ちます:)
Valentin Galea

元の投稿を更新して、完了したばかりのソリューションを含めました。
チャックD
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.