Jak wygenerować tor wyścigowy 3D z splajnu?

9

Chcę wygenerować trójwymiarowy tor wyścigowy wokół splajnu, który opisuje jego kształt. Oto przykładowy film .

Ścieżka powinna być niekończącym się tunelem, który rozciąga się wzdłuż trójwymiarowego splajnu, z pewnymi przeszkodami. Moim najlepszym pomysłem do tej pory było wyciągnięcie kształtu okręgu wzdłuż splajnu.

Chciałbym również uzyskać wskazówki dotyczące zarządzania geometrią, tj. Tworzenia jej z operacji wyciągnięcia po profilach, zarządzania „cyklem życia” pamięci, kolizji i teksturowania.

Valentin Galea
źródło
1
Wygląda bardzo podobnie do proun: proun-game.com . Jeśli nie ma problemów z prawami autorskimi, możesz uzyskać dobrą odpowiedź od autora tej gry. Możesz uzyskać jego informacje tutaj: blog.oogst3d.net .
Vite Falcon,
tak, myślałem o tym - TNX!
Valentin Galea

Odpowiedzi:

5

Nie jestem pewien, w jakim języku pracujesz, ale tutaj znajduje się przykład procedury ekstruzji siatki dla Unity3D:

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

Jestem pewien, że możesz spojrzeć na kod i przerobić go w swojej sytuacji.

EDYCJA: Pracuję nad grą, która wykorzystuje proceduralny system szyny wytłaczanej, taki jak ten, który zaczynasz, ale jest w C # w Unity3d. Dam ci przegląd tego, jak tworzę moje wytłoczenie szyny na podstawie sześciennej ścieżki Beziera, więc chociaż siatka szyny jest generowana proceduralnie, jest oparta na ścieżce Beziera, którą zdefiniowałem wcześniej w edytorze. To byłoby jak edytor poziomów w przypadku twojej gry, w moim przypadku projektuje ona stoły do ​​pinballu. Poniżej znajduje się przykład tego, jak to robię:

1.) Zbuduj / znajdź i zaimplementuj klasę ścieżki Beziera. Otrzymasz dane źródłowe do wyciągnięcia siatki. Jest jeden w C #, który można przenieść do c ++.

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

2.) Po utworzeniu ścieżki Beziera próbkowane są punkty danych z tej ścieżki. Można to zrobić za pomocą metody Interp dla klasy podanej powyżej. Otrzymasz listę / tablicę punktów Vector3 wzdłuż ścieżki Beziera.

3.) Utwórz klasę pomocnika do konwersji danych ścieżki Vector3 Beziera z kroku 2. W tym przypadku mam prostą klasę o nazwie ExtrudedTrailSection, jak zdefiniowano poniżej:

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

    public ExtrudedTrailSection() { }
}

4.) Iteruj przez przykładowe dane Vector3 i przekonwertuj na tablicę ExtrudedTrailSections dostarczającą przykładowe dane i macierz podstawową, która byłaby miejscem głównym wyciągniętej siatki.

  1. ) Użyj tablicy ExtrudedTrailSections, aby utworzyć tablicę końcowej Matrix4x4 [] przy użyciu następującego kodu:

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.) Teraz masz tablicę Matrix4x4 [] i możesz wyciągnąć siatkę, ale najpierw potrzebujemy siatki referencyjnej, z której można wyciągnąć. Mam klasę użytkową, która utworzy okrągłą powierzchnię siatki, którą dostarczymy do metody wytłaczania siatki.

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.) Znajdź centrum tych danych:

    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.) Teraz, gdy mamy dane krawędzi i środka dla promieniowej siatki powierzchni, możesz zbudować obiekt siatki na podstawie swoich danych. Końcowy wierzchołek siatki jest obliczonym przez nas punktem środkowym. Ostateczna siatka jest tylko powierzchnią dostarczaną do metody wytłaczania siatki, którą podałem jako przykład w proceduralnej klasie wytłaczania siatki pakietu Unity. Ponownie, to jest moja metoda i oczywiście musiałbyś wprowadzić te dane do OpenGL. Jeśli masz bibliotekę narzędziową 3D, której używasz lub możesz napisać własną klasę siatki, prawdopodobnie lepiej byłoby wygenerować ostateczną wyciągniętą siatkę, ponieważ dane te nie są tak naprawdę potrzebne OpenGL do renderowania. Ta siatka powierzchni jest po prostu używana jako odniesienie dla wyciągnięcia siatki.

    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.) Znajdź zewnętrzne krawędzie okrągłej siatki zgodnie z potrzebami metody wytłaczania siatki. Ponownie ten kod jest dostarczany w pakiecie jedności.

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.) Wprowadź wszystkie te dane do metody wytłaczania siatki.

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;
}

Ostateczne wyjście w moim przypadku wygląda tak ...

wprowadź opis zdjęcia tutaj

Powodzenia, twoja gra wygląda naprawdę fajnie! Daj mi znać, jeśli to wymyślisz?

Głaskanie pod brodę

Chuck D.
źródło
Interesuje mnie przede wszystkim OpenGL i C ++, ale „pseudokod” powinien również pomóc :)
Valentin Galea
Zaktualizowałem swój oryginalny post, aby uwzględnić rozwiązanie, które właśnie ukończyłem.
Chuck D