Comment générer un circuit 3D à partir d'une spline?

9

Je veux générer une piste de course en 3 dimensions autour d'une spline qui décrit sa forme. Voici une vidéo illustrative .

La piste devrait être un tunnel sans fin qui longe une spline 3D, avec quelques obstacles jetés. Ma meilleure idée jusqu'à présent a été de créer une forme de cercle le long de la spline.

Je voudrais également quelques conseils sur la façon de gérer la géométrie, c'est-à-dire comment la créer à partir de l'opération loft, gérer son «cycle de vie» en mémoire, collision et texturation.

Valentin Galea
la source
1
Ressemble beaucoup à proun : proun-game.com . S'il n'y a pas de problèmes de droits d'auteur, vous pourriez obtenir une bonne réponse de l'auteur de ce jeu. Vous pouvez obtenir ses informations ici: blog.oogst3d.net .
Vite Falcon
ouais je pensais à ça - tnx!
Valentin Galea

Réponses:

5

Je ne sais pas dans quelle langue vous travaillez, mais il existe un exemple d'extrusion de maillage procédural pour Unity3D situé ici:

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

Je suis sûr que vous pourriez regarder le code et le retravailler pour votre situation.

EDIT: Je travaille sur un jeu qui utilise un système de rail extrudé procédural comme celui que vous démarrez, mais il est en C # dans Unity3d. Je vais vous donner un aperçu de la façon dont je crée mon extrusion de rail en fonction d'un chemin de Bézier cubique, donc bien que le maillage du rail soit généré de manière procédurale, il est basé sur le chemin de Bézier que je définis à l'avance dans un éditeur. Ce serait comme un éditeur de niveau dans le cas de votre jeu, dans mon cas, c'est la conception de flippers. Voici un exemple de la façon dont je le fais:

1.) Construire / rechercher et implémenter une classe de chemin de Bézier. Cela vous donnera les données source pour votre extrusion de maillage. Il y en a un en C # ici que vous pouvez porter en c ++.

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

2.) Une fois que vous avez créé un chemin de Bézier, les points de données de ce chemin sont échantillonnés. Cela peut être fait via la méthode Interp sur la classe fournie ci-dessus. Cela vous donnera une liste / tableau de points Vector3 le long du chemin de Bézier.

3.) Créez une classe d'assistance pour convertir les données de chemin de Bézier Vector3 de l'étape 2. Dans ce cas, j'ai une classe simple appelée ExtrudedTrailSection comme défini ci-dessous:

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

    public ExtrudedTrailSection() { }
}

4.) Parcourez vos données d'exemple Vector3 et convertissez-les en un tableau d'ExtrudedTrailSections en lui fournissant les données d'exemple et une matrice de base qui serait l'emplacement racine de votre maillage extrudé.

  1. ) Utilisez le tableau ExtrudedTrailSections pour créer un tableau de Matrix4x4 [] final à l'aide du code suivant:

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.) Vous avez maintenant un tableau de Matrix4x4 [] et vous pouvez extruder un maillage, mais nous avons d'abord besoin d'un maillage de référence pour extruder. J'ai une classe d'utilité qui créera une face de maillage circulaire que nous fournirons à la méthode d'extrusion de maillage.

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.) Trouvez le centre de ces données:

    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.) Maintenant que nous avons les données d'arête et de centre pour un maillage de face radiale, vous pouvez construire un objet maillé à l'aide de vos données. Le sommet final du maillage est le point central que nous avons calculé. Le maillage final est juste une face qui est fournie à la méthode d'extrusion de maillage dont j'ai fourni un exemple dans la classe d'extrusion de maillage procédural du package Unity. Encore une fois, c'est ma méthode et, évidemment, vous devrez alimenter ces données dans OpenGL. Si vous avez une bibliothèque d'utilitaires 3D que vous utilisez ou pouvez écrire votre propre classe de maillage, cela fonctionnerait probablement mieux pour générer votre maillage extrudé final car ces données ne sont pas vraiment nécessaires à opengl pour le rendu. Ce maillage de face est juste utilisé comme référence pour l'extrusion du maillage.

    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.) Trouvez les bords extérieurs du maillage circulaire selon les besoins par la méthode d'extrusion du maillage. Encore une fois, ce code est fourni dans le package d'unité.

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.) Alimentez toutes ces données dans la méthode d'extrusion de maillage.

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

La sortie finale dans mon cas ressemble à ceci ..

entrez la description de l'image ici

Bonne chance, votre jeu est vraiment cool! Faites-moi savoir si vous le comprenez?

Mandrin

Chuck D
la source
Je suis principalement intéressé par OpenGL et C ++, mais le 'pseudocode' devrait aussi aider :)
Valentin Galea
J'ai mis à jour mon message d'origine pour inclure une solution que je viens de terminer.
Chuck D