스플라인에서 3D 레이스 트랙을 어떻게 생성합니까?


9

모양을 설명하는 스플라인 주위에 3 차원 레이스 트랙을 생성하고 싶습니다. 다음은 예시적인 비디오 입니다.

트랙은 3D 스플라인을 따라 휩쓸고 끝없는 터널이되어야합니다. 몇 가지 장애물이 있습니다. 지금까지 가장 좋은 아이디어는 스플라인을 따라 원형을 로프트하는 것입니다.

또한 지오메트리를 관리하는 방법, 즉 로프트 작업에서 지오메트리를 생성하고 메모리, 충돌 및 텍스처링에서 '라이프 사이클'을 관리하는 방법에 대한 몇 가지 지침을 원합니다.


1
proun : proun-game.com 과 매우 비슷 합니다. 저작권 문제가 없다면이 게임의 저자로부터 좋은 답변을 얻을 수 있습니다. 그의 정보를 얻을 수 있습니다 : blog.oogst3d.net .
Vite Falcon

그래, 나는 그것에 대해 생각하고 있었다-tnx!
Valentin Galea

답변:


5

어떤 언어로 작업하고 있는지 잘 모르겠지만 여기에 Unity3D에 대한 절차 적 메쉬 압출 예제가 있습니다.

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

코드를보고 상황에 맞게 재 작업 할 수 있다고 확신합니다.

편집 : 나는 당신이 시작하는 것과 같은 절차 적 압출 레일 시스템을 사용하는 게임을 만들고 있지만 Unity3d의 C #에 있습니다. 큐빅 베 지어 경로를 기반으로 레일 돌출을 작성하는 방법에 대한 개요를 제공하므로 레일의 메시가 절차 적으로 생성되지만 편집기에서 미리 정의한 베 지어 경로를 기반으로합니다. 게임의 경우 레벨 편집기와 같을 것입니다. 제 경우에는 핀볼 테이블을 디자인하고 있습니다. 아래 목록은 내가하는 일의 예입니다.

1.) 베 지어 패스 클래스를 구축 / 찾기 및 구현합니다. 메시 압출의 소스 데이터가 표시됩니다. C #에는 C ++로 이식 할 수있는 것이 하나 있습니다.

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.) 메쉬 압출 방법으로 필요에 따라 원형 메쉬의 바깥 쪽 가장자리를 찾으십시오. 이 코드는 유니티 패키지로 제공됩니다.

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

방금 완료 한 솔루션을 포함하도록 원래 게시물을 업데이트했습니다.
Chuck D
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.