形状を表すスプラインの周りに3次元のレーストラックを生成したい。 こちらがビデオの例です。
トラックは、いくつかの障害物が投げ込まれた3Dスプラインに沿ってスイープするエンドレストンネルである必要があります。私のこれまでの最良のアイデアは、スプラインに沿って円形状をロフトすることでした。
また、ジオメトリを管理する方法、つまりロフト操作からジオメトリを作成する方法、メモリ内の「ライフサイクル」を管理する方法、コリジョン、テクスチャリングへのポインタも必要です。
形状を表すスプラインの周りに3次元のレーストラックを生成したい。 こちらがビデオの例です。
トラックは、いくつかの障害物が投げ込まれた3Dスプラインに沿ってスイープするエンドレストンネルである必要があります。私のこれまでの最良のアイデアは、スプラインに沿って円形状をロフトすることでした。
また、ジオメトリを管理する方法、つまりロフト操作からジオメトリを作成する方法、メモリ内の「ライフサイクル」を管理する方法、コリジョン、テクスチャリングへのポインタも必要です。
回答:
使用している言語はわかりませんが、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の配列に変換して、サンプルデータと、押し出しメッシュのルート位置となるベースマトリックスを提供します。
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;
}
私の場合の最終的な出力は次のようになります。
頑張って、あなたのゲームは本当にクールに見えます!わかりましたら教えてください。
チャック