using System.Collections.Generic; using UnityEngine; namespace Kayac { public static class MeshGenerator { // 平面を作る。法線はCross(forward, right)。右と前を渡せば上向きになる。 public static bool GenerateQuad( out Vector3[] vertices, out Vector3[] normals, out Vector2[] uvs, out int[] indices, Vector3 origin, Vector3 right, Vector3 forward, Vector2 leftBackUv, Vector2 rightForwardUv, bool doubleSided) { int vertexCount = doubleSided ? 8 : 4; int indexCount = doubleSided ? 12 : 6; vertices = new Vector3[vertexCount]; normals = new Vector3[vertexCount]; uvs = new Vector2[vertexCount]; indices = new int[indexCount]; vertices[0] = origin; vertices[1] = origin + forward; vertices[2] = origin + forward + right; vertices[3] = origin + right; var n = Vector3.Cross(forward, right).normalized; normals[0] = normals[1] = normals[2] = normals[3] = n; uvs[0] = new Vector2(leftBackUv.x, leftBackUv.y); uvs[1] = new Vector2(rightForwardUv.x, leftBackUv.y); uvs[2] = new Vector2(rightForwardUv.x, rightForwardUv.y); uvs[3] = new Vector2(leftBackUv.x, rightForwardUv.y); var indexPos = SetQuad(indices, 0, 0, 1, 2, 3); if (doubleSided) { vertices[4] = vertices[3]; vertices[5] = vertices[2]; vertices[6] = vertices[1]; vertices[7] = vertices[0]; normals[4] = normals[5] = normals[6] = normals[7] = -n; uvs[4] = uvs[3]; uvs[5] = uvs[2]; uvs[6] = uvs[1]; uvs[7] = uvs[0]; SetQuad(indices, indexPos, 4, 5, 6, 7); } return true; } public static bool GenerateQuad( Mesh mesh, Vector3 origin, Vector3 right, Vector3 forward, Vector2 leftBackUv, Vector2 rightForwardUv, bool doubleSided) { Vector3[] vertices; Vector3[] normals; Vector2[] uvs; int[] indices; var ret = false; if (GenerateQuad(out vertices, out normals, out uvs, out indices, origin, right, forward, leftBackUv, rightForwardUv, doubleSided)) { FillMesh(mesh, vertices, normals, uvs, indices); ret = true; } return ret; } // XZ平面上の点列をもらって、一定高さの壁を作る public static bool GenerateWall( out Vector3[] vertices, out Vector3[] normals, out Vector2[] uvs, out int[] indices, IList positions, float height, float thickness, bool looped, bool separateFaces, int topDiv = 1) { int verticesPerPoint = 3 + topDiv; int indicesPerBand = (verticesPerPoint - 1) * 6; vertices = new Vector3[positions.Count * verticesPerPoint]; normals = new Vector3[vertices.Length]; uvs = new Vector2[vertices.Length]; var indexCount = (positions.Count - 1) * indicesPerBand; if (looped) { indexCount += indicesPerBand; } else { indexCount += ((verticesPerPoint - 2) * 2) * 3; // 両端の蓋 } indices = new int[indexCount]; int vi = 0; float halfThickness = 0.5f * thickness; float vSide = height / ((height * 2f) + thickness); float vDiv = thickness / (topDiv * ((height * 2f) + thickness)); for (int i = 0; i < positions.Count; i++) { Vector2 prev; Vector2 next; if (i > 0) { prev = positions[i - 1]; } else if (looped) { prev = positions[positions.Count - 1]; } else { prev = positions[0]; } if (i < (positions.Count - 1)) { next = positions[i + 1]; } else if (looped) { next = positions[0]; } else { next = positions[positions.Count - 1]; } Vector2 tangent; if (prev == positions[i]) { tangent = next - positions[i]; } else if (next == positions[i]) { tangent = positions[i] - prev; } else { tangent = (positions[i] - prev).normalized + (next - positions[i]).normalized; } tangent.Normalize(); Vector2 normal; // tangentを2D平面で90度回したもの(上から見て反時計周り) normal.x = -tangent.y; normal.y = tangent.x; var inner = positions[i] - (normal * halfThickness); var outer = positions[i] + (normal * halfThickness); var innerN = new Vector3(-normal.x, 0f, -normal.y); var outerN = new Vector3(normal.x, 0f, normal.y); // 接地頂点 float u = (float)i / (float)(positions.Count - 1); vertices[vi] = new Vector3(inner.x, 0f, inner.y); normals[vi] = innerN; uvs[vi] = new Vector2(u, 0f); vi++; vertices[vi] = new Vector3(inner.x, height, inner.y); normals[vi] = innerN; uvs[vi] = new Vector2(u, vSide); vi++; var tangent3d = new Vector3(tangent.x, 0f, tangent.y); for (int j = 1; j < topDiv; j++) { var rad = Mathf.PI * (float)j / (float)topDiv; var n = RotateVector(innerN, tangent3d, rad); vertices[vi] = new Vector3(positions[i].x, height, positions[i].y) + (n * halfThickness); normals[vi] = n; uvs[vi] = new Vector2(u, vSide + (vDiv * j)); vi++; } vertices[vi] = new Vector3(outer.x, height, outer.y); normals[vi] = outerN; uvs[vi] = new Vector2(u, 1f - vSide); vi++; vertices[vi] = new Vector3(outer.x, 0f, outer.y); normals[vi] = outerN; uvs[vi] = new Vector2(u, 1f); vi++; } var indexPos = FillStripIndices(indices, 0, 0, positions.Count - 1, 2 + topDiv, looped); if (!looped) { for (int i = 2; i < verticesPerPoint; i++) { indices[indexPos + 0] = 0; indices[indexPos + 1] = i - 1; indices[indexPos + 2] = i; indexPos += 3; indices[indexPos + 0] = vi - verticesPerPoint; indices[indexPos + 1] = vi - verticesPerPoint + i - 1; indices[indexPos + 2] = vi - verticesPerPoint + i; indexPos += 3; } } Debug.Assert(indexCount == indexPos); if (separateFaces) { SeparateFaces(out vertices, out normals, out uvs, out indices, vertices, uvs, indices); } return true; } public static bool GenerateWall( Mesh mesh, IList positions, float height, float thickness, bool looped, bool separateFaces, int topDiv = 1) { Vector3[] vertices; Vector3[] normals; Vector2[] uvs; int[] indices; var ret = false; if (GenerateWall(out vertices, out normals, out uvs, out indices, positions, height, thickness, looped, separateFaces, topDiv)) { FillMesh(mesh, vertices, normals, uvs, indices); ret = true; } return ret; } // XZ平面上の点列をもらって、一定の厚さの凸多角形を作る public static bool GenerateConvexPolygon( out Vector3[] vertices, out Vector3[] normals, out int[] indices, IList positions, float height, bool separateFaces, float roundingRadius = 0f, int roundingDiv = 1) { int n = positions.Count; int verticesPerPoint = 2 + roundingDiv; int indicesPerBand = ((verticesPerPoint - 1) * 6) + (3 + 3); vertices = new Vector3[(n * verticesPerPoint) + 2]; normals = new Vector3[vertices.Length]; var indexCount = n * indicesPerBand; indices = new int[indexCount]; // 三点見て舐める方向を確定する。 Debug.Assert(n >= 3); var v0 = positions[0]; var v1 = positions[1]; var v2 = positions[2]; // 時計周りであれば、cross(v01,v12)>0 var v01 = v1 - v0; var v12 = v2 - v1; var cross = (v01.x * v12.y) - (v01.y * v12.x); int iBegin, iEnd, iInc; if (cross > 0f) { iBegin = 0; iEnd = n; iInc = 1; } else { iBegin = n - 1; iEnd = -1; iInc = -1; } int vi = 0; // 下面中央と上面中央を空けておく var g = Vector2.zero; //重心 int i = iBegin; int nextIndex, prevIndex; var nDown = new Vector3(0f, -1f, 0f); var nUp = new Vector3(0f, 1f, 0f); while (i != iEnd) { nextIndex = (i + iInc + n) % n; prevIndex = (i - iInc + n) % n; var prev = positions[prevIndex]; var next = positions[nextIndex]; var p01 = (positions[i] - prev).normalized; var p12 = (next - positions[i]).normalized; var tangent = p01 + p12; tangent.Normalize(); var tangent3d = new Vector3(tangent.x, 0f, tangent.y); Vector3 normal; // tangentをXZ平面で90度回したもの normal.x = tangent.y; normal.y = -tangent.x; var n3d = new Vector3(normal.x, 0f, normal.y); // 接地頂点 vertices[vi] = new Vector3(positions[i].x, 0f, positions[i].y); normals[vi] = n3d; vi++; var c = new Vector3(positions[i].x, height, positions[i].y); c -= n3d * roundingRadius; for (int j = 0; j <= roundingDiv; j++) { var rad = Mathf.PI * 0.5f * (float)j / (float)roundingDiv; var vn = RotateVector(n3d, tangent3d, rad); vertices[vi] = c + (vn * roundingRadius); normals[vi] = vn; vi++; } g += positions[i]; i += iInc; } // 重心確定 g /= (float)positions.Count; vertices[vi] = new Vector3(g.x, 0f, g.y); normals[vi] = nDown; vi++; vertices[vi] = new Vector3(g.x, height + roundingRadius, g.y); normals[vi] = nUp; vi++; var indexStart = FillStripIndices(indices, 0, 0, n - 1, 1 + roundingDiv, looped: true); // ファンを生成 prevIndex = n - 1; for (i = 0; i < n; i++) { indices[indexStart + 0] = vi - 2; // 重心下面 indices[indexStart + 1] = (prevIndex * verticesPerPoint); indices[indexStart + 2] = (i * verticesPerPoint); indexStart += 3; indices[indexStart + 0] = vi - 1; // 重心上面 indices[indexStart + 1] = ((i + 1) * verticesPerPoint) - 1; indices[indexStart + 2] = ((prevIndex + 1) * verticesPerPoint) - 1; indexStart += 3; prevIndex = i; } Debug.Assert(indexStart == indexCount); if (separateFaces) { SeparateFaces(out vertices, out normals, out indices, vertices, indices); } return true; } public static bool GenerateConvexPolygon( Mesh mesh, IList positions, float height, bool separateFaces, float roundingRadius = 0f, int roundingDiv = 1) { Vector3[] vertices; Vector3[] normals; int[] indices; var ret = false; if (GenerateConvexPolygon(out vertices, out normals, out indices, positions, height, separateFaces, roundingRadius, roundingDiv)) { FillMesh(mesh, vertices, normals, null, indices); ret = true; } return ret; } public static bool GenerateSphere( out Vector3[] verticesOut, out Vector3[] normalsOut, out int[] indicesOut, int subdivision, bool separateFaces) { if (subdivision > 6) { verticesOut = normalsOut = null; indicesOut = null; return false; } GenerateOctahedron(out verticesOut, out normalsOut, out indicesOut, separateFaces: false); var table = new VertexEdgeFaceTable(verticesOut, normalsOut, null, indicesOut); for (int i = 0; i < subdivision; i++) { table.SubDivide(); } // 全頂点を球面に移動 var vertices = table.Vertices; for (int i = 0; i < vertices.Count; i++) { var v = vertices[i]; v.position.Normalize(); v.position *= 0.5f; // 半径は0.5に v.normal = v.position; } table.GetArrays(out verticesOut, out normalsOut, out indicesOut); if (separateFaces) { SeparateFaces(out verticesOut, out normalsOut, out indicesOut, verticesOut, indicesOut); } return true; } public static bool GenerateSphere( Mesh mesh, int subdivision, bool separateFaces) { Vector3[] vertices; Vector3[] normals; int[] indices; var ret = false; if (GenerateSphere(out vertices, out normals, out indices, subdivision, separateFaces)) { FillMesh(mesh, vertices, normals, null, indices); ret = true; } return ret; } public static bool GenerateCylinderSide( Mesh mesh, float height, float radius, int subdivision, bool separateFaces) { Vector3[] vertices; Vector3[] normals; Vector2[] uvs; int[] indices; var ret = false; if (GenerateCylinderSide(out vertices, out normals, out uvs, out indices, height, radius, subdivision, separateFaces)) { FillMesh(mesh, vertices, normals, uvs, indices); ret = true; } return ret; } // 円柱。divは面分割数。最低2だが、2だとただの面になる。 public static bool GenerateCylinderSide( out Vector3[] vertices, out Vector3[] normals, out Vector2[] uvs, out int[] indices, float height, float radius, int subdivision, bool separateFaces) { int div = 4 << subdivision; if ((div < 2) || (div >= 0x8000)) { vertices = normals = null; indices = null; uvs = null; return false; } vertices = new Vector3[div * 2]; normals = new Vector3[vertices.Length]; uvs = new Vector2[vertices.Length]; var indexCount = div * 6; indices = new int[indexCount]; var hHeight = height * 0.5f; int vi = 0; for (int i = 0; i < div; i++) { var t = (float)i / (float)div; var angle = 2f * Mathf.PI * t; var x = Mathf.Cos(angle) * radius; var z = Mathf.Sin(angle) * radius; vertices[vi] = new Vector3(x, -hHeight, z); normals[vi] = new Vector3(x, 0f, z); uvs[vi] = new Vector2(t, 0f); vi++; vertices[vi] = new Vector3(x, hHeight, z); normals[vi] = new Vector3(x, 0f, z); uvs[vi] = new Vector2(t, 1f); vi++; } FillStripIndices(indices, 0, 0, div - 1, 1, true); if (separateFaces) { SeparateFaces(out vertices, out normals, out uvs, out indices, vertices, uvs, indices); } return true; } // 角で法線共有しているために滑らか。基本再分割してより滑らかな図形を作るための種として使う。 public static void GenerateCube( out Vector3[] vertices, out Vector3[] normals, out int[] indices, bool separateFaces) { vertices = new Vector3[8]; indices = new int[36]; vertices[0] = new Vector3(-0.5f, -0.5f, -0.5f); vertices[1] = new Vector3(-0.5f, -0.5f, 0.5f); vertices[2] = new Vector3(-0.5f, 0.5f, -0.5f); vertices[3] = new Vector3(-0.5f, 0.5f, 0.5f); vertices[4] = new Vector3(0.5f, -0.5f, -0.5f); vertices[5] = new Vector3(0.5f, -0.5f, 0.5f); vertices[6] = new Vector3(0.5f, 0.5f, -0.5f); vertices[7] = new Vector3(0.5f, 0.5f, 0.5f); normals = vertices; var start = 0; start = SetQuad(indices, start, 0, 1, 3, 2); start = SetQuad(indices, start, 4, 5, 7, 6); start = SetQuad(indices, start, 0, 4, 6, 2); start = SetQuad(indices, start, 1, 5, 7, 3); start = SetQuad(indices, start, 0, 1, 5, 4); start = SetQuad(indices, start, 2, 3, 7, 6); if (separateFaces) { SeparateFaces(out vertices, out normals, out indices, vertices, indices); } } // 角で法線共有しているために滑らか。基本再分割してより滑らかな図形を作るための種として使う。 public static void GenerateTetrahedron( out Vector3[] vertices, out Vector3[] normals, out int[] indices, bool separateFaces) { vertices = new Vector3[4]; indices = new int[12]; vertices[0] = new Vector3(0f, 1f, 0f); vertices[1] = new Vector3(Mathf.Sqrt(8f / 9f), -1f / 3f, 0f); vertices[2] = new Vector3(-Mathf.Sqrt(2f / 9f), -1f / 3f, Mathf.Sqrt(2f / 3f)); vertices[3] = new Vector3(-Mathf.Sqrt(2f / 9f), -1f / 3f, -Mathf.Sqrt(2f / 3f)); normals = vertices; var start = 0; start = SetTriangle(indices, start, 0, 2, 1); start = SetTriangle(indices, start, 0, 3, 2); start = SetTriangle(indices, start, 0, 1, 3); start = SetTriangle(indices, start, 1, 2, 3); if (separateFaces) { SeparateFaces(out vertices, out normals, out indices, vertices, indices); } } // 角で法線共有しているために滑らか。基本再分割してより滑らかな図形を作るための種として使う。 public static void GenerateOctahedron( out Vector3[] vertices, out Vector3[] normals, out int[] indices, bool separateFaces) { vertices = new Vector3[6]; indices = new int[24]; vertices[0] = new Vector3(-0.5f, 0f, 0f); vertices[1] = new Vector3(0f, -0.5f, 0f); vertices[2] = new Vector3(0f, 0f, -0.5f); vertices[3] = new Vector3(0f, 0f, 0.5f); vertices[4] = new Vector3(0f, 0.5f, 0f); vertices[5] = new Vector3(0.5f, 0f, 0f); normals = vertices; var start = 0; start = SetTriangle(indices, start, 0, 1, 2); start = SetTriangle(indices, start, 0, 2, 4); start = SetTriangle(indices, start, 3, 1, 0); start = SetTriangle(indices, start, 3, 0, 4); start = SetTriangle(indices, start, 5, 1, 3); start = SetTriangle(indices, start, 5, 3, 4); start = SetTriangle(indices, start, 2, 1, 5); start = SetTriangle(indices, start, 2, 5, 4); if (separateFaces) { SeparateFaces(out vertices, out normals, out indices, vertices, indices); } } // 全ての共有頂点を複製し、法線の再計算を行う public static void SeparateFaces( out Vector3[] verticesOut, out Vector3[] normalsOut, out Vector2[] uvsOut, out int[] indicesOut, IList verticesIn, IList uvsIn, IList indicesIn) { Debug.Assert((indicesIn.Count % 3) == 0); verticesOut = new Vector3[indicesIn.Count]; normalsOut = new Vector3[indicesIn.Count]; uvsOut = null; if (uvsIn != null) { uvsOut = new Vector2[indicesIn.Count]; } indicesOut = new int[indicesIn.Count]; // モリモリ複製しつつ法線は再計算する for (int i = 0; i < indicesIn.Count; i += 3) { var i0 = indicesIn[i + 0]; var i1 = indicesIn[i + 1]; var i2 = indicesIn[i + 2]; var v0 = verticesIn[i0]; var v1 = verticesIn[i1]; var v2 = verticesIn[i2]; verticesOut[i + 0] = verticesIn[i0]; verticesOut[i + 1] = verticesIn[i1]; verticesOut[i + 2] = verticesIn[i2]; if (uvsIn != null) { uvsOut[i + 0] = uvsIn[i0]; uvsOut[i + 1] = uvsIn[i1]; uvsOut[i + 2] = uvsIn[i2]; } var v10 = v1 - v0; var v20 = v2 - v0; var n = Vector3.Cross(v10, v20); n.Normalize(); normalsOut[i + 0] = normalsOut[i + 1] = normalsOut[i + 2] = n; // インデクスは単純 indicesOut[i + 0] = i + 0; indicesOut[i + 1] = i + 1; indicesOut[i + 2] = i + 2; } } public static void SeparateFaces( out Vector3[] verticesOut, out Vector3[] normalsOut, out int[] indicesOut, IList verticesIn, IList indicesIn) { // UV作っちゃって捨てる Vector2[] uvsOutUnused = null; SeparateFaces( out verticesOut, out normalsOut, out uvsOutUnused, out indicesOut, verticesIn, null, indicesIn); } // non-public ------------------ static int FillStripIndices(int[] indices, int indexStart, int vertexStart, int stripLength, int stripWidth, bool looped) { int vi = vertexStart; int vUnit = stripWidth + 1; for (int li = 0; li < stripLength; li++) { for (int wi = 0; wi < stripWidth; wi++) { indexStart = SetQuad( indices, indexStart, vi + wi, vi + wi + 1, vi + vUnit + wi + 1, vi + vUnit + wi); } vi += vUnit; } if (looped) { for (int wi = 0; wi < stripWidth; wi++) { indexStart = SetQuad( indices, indexStart, vi + wi, vi + wi + 1, wi + 1, wi); } } return indexStart; } static void FillMesh( Mesh mesh, Vector3[] vertices, Vector3[] normals, Vector2[] uvs, int[] indices) { mesh.Clear(); mesh.vertices = vertices; mesh.normals = normals; if (uvs != null) { mesh.uv = uvs; } mesh.SetIndices(indices, MeshTopology.Triangles, 0); } static int SetTriangle(int[] indices, int start, int v0, int v1, int v2) { indices[start + 0] = v0; indices[start + 1] = v1; indices[start + 2] = v2; return start + 3; } static int SetQuad(int[] indices, int start, int v0, int v1, int v2, int v3) { indices[start + 0] = v0; indices[start + 1] = v1; indices[start + 2] = v2; indices[start + 3] = v2; indices[start + 4] = v3; indices[start + 5] = v0; return start + 6; } static Vector3 RotateVector( Vector3 v, Vector3 axisNormalized, // 軸ベクトルは要正規化 float radian) { // vを軸に射影して、回転円中心cを得る var c = ProjectVector(v, axisNormalized); var p = v - c; // p及びaと直交するベクタを得る var q = Vector3.Cross(axisNormalized, p); // a,pは直交しているから、|q|=|p| // 回転後のv'の終点V'は、V' = C + s*p + t*q と表せる。 // ここで、s = cosθ t = sinθ var s = Mathf.Cos(radian); var t = Mathf.Sin(radian); return c + (p * s) + (q * t); } static Vector3 ProjectVector( Vector3 v, Vector3 axisNormalized) { return Vector3.Dot(v, axisNormalized) * axisNormalized; } } public class VertexEdgeFaceTable { public VertexEdgeFaceTable( IList positions, IList normals, IList uvs, IList indices) { vertices = new List(); edges = new List(); faces = new List(); // まず頂点充填 Debug.Assert(positions.Count == normals.Count); if (uvs != null) { Debug.Assert(positions.Count == uvs.Count); for (int i = 0; i < positions.Count; i++) { vertices.Add(new Vertex(positions[i], normals[i], uvs[i])); } } else { for (int i = 0; i < positions.Count; i++) { vertices.Add(new Vertex(positions[i], normals[i], Vector2.zero)); } } // 次に辺を充填。HashSetで重複を回避する。 var edgeSet = new HashSet(); for (int i = 0; i < indices.Count; i += 3) { var vi0 = indices[i + 0]; var vi1 = indices[i + 1]; var vi2 = indices[i + 2]; var e01 = EdgeKey(vi0, vi1); var e12 = EdgeKey(vi1, vi2); var e20 = EdgeKey(vi2, vi0); if (!edgeSet.Contains(e01)) { edgeSet.Add(e01); } if (!edgeSet.Contains(e12)) { edgeSet.Add(e12); } if (!edgeSet.Contains(e20)) { edgeSet.Add(e20); } } // 辺セットを配列に充填しつつ、頂点インデクス→辺インデクスの辞書を用意 var edgeMap = new Dictionary(); foreach (var edgeKey in edgeSet) { var vi0 = (int)(edgeKey >> 16); var vi1 = (int)(edgeKey & 0xffff); edgeMap.Add(edgeKey, edges.Count); edges.Add(new Edge(vi0, vi1)); } // 面を充填開始 for (int i = 0; i < indices.Count; i += 3) { var vi0 = indices[i + 0]; var vi1 = indices[i + 1]; var vi2 = indices[i + 2]; var e01 = EdgeKey(vi0, vi1); var e12 = EdgeKey(vi1, vi2); var e20 = EdgeKey(vi2, vi0); var ei01 = edgeMap[e01]; var ei12 = edgeMap[e12]; var ei20 = edgeMap[e20]; faces.Add(new Face(ei01, ei12, ei20)); } } public IList Vertices { get { return vertices; } } public override string ToString() { var sb = new System.Text.StringBuilder(); sb.AppendLine("[Vertices]"); for (int i = 0; i < vertices.Count; i++) { var v = vertices[i]; sb.AppendFormat("\t{0}: {1} {1} {2}\n", i, v.position, v.normal, v.uv); } sb.AppendLine("[Edges]"); for (int i = 0; i < edges.Count; i++) { sb.AppendFormat("\t{0}: {1} {2}\n", i, edges[i].v0, edges[i].v1); } sb.AppendLine("[Faces]"); for (int i = 0; i < faces.Count; i++) { sb.AppendFormat("\t{0}: {1} {2} {3}\n", i, faces[i].e0, faces[i].e1, faces[i].e2); } return sb.ToString(); } // 全ての辺を二等分して頂点を生成し、分割面を生成する。面の数は4倍になる。 public void SubDivide() { var oldVn = vertices.Count; var oldEn = edges.Count; var oldFn = faces.Count; // 全edgeの中点を生成して追加 for (int i = 0; i < oldEn; i++) { var edge = edges[i]; var v0 = vertices[edge.v0]; var v1 = vertices[edge.v1]; var midPoint = (v0.position + v1.position) * 0.5f; var midNormal = (v0.normal + v1.normal).normalized; var midUv = (v0.uv + v1.uv) * 0.5f; vertices.Add(new Vertex(midPoint, midNormal, midUv)); } // faceの分割。edgeが古いうちにやる for (int i = 0; i < oldFn; i++) { var face = faces[i]; var e0 = edges[face.e0]; var e1 = edges[face.e1]; var e2 = edges[face.e2]; // 4分割する // 関連する辺は3 -> 9 // e0の中点とe1の中点からなる新エッジ = oldEn + (i * 3) + 0 // e1の中点とe2の中点からなる新エッジ = oldEn + (i * 3) + 1 // e2の中点とe0の中点からなる新エッジ = oldEn + (i * 3) + 2 // e0は、e0とoldEn + (oldFn * 3) + e0 に分割 // e1は、e1とoldEn + (oldFn * 3) + e1 に分割 // e2は、e2とoldEn + (oldFn * 3) + e2 に分割 var newFace0 = MakeDividedFace(i, 0, oldEn, oldFn, face.e0, face.e1); var newFace1 = MakeDividedFace(i, 1, oldEn, oldFn, face.e1, face.e2); var newFace2 = MakeDividedFace(i, 2, oldEn, oldFn, face.e2, face.e0); faces.Add(newFace0); faces.Add(newFace1); faces.Add(newFace2); // 辺を生成 var newEdge0 = new Edge(oldVn + face.e0, oldVn + face.e1); var newEdge1 = new Edge(oldVn + face.e1, oldVn + face.e2); var newEdge2 = new Edge(oldVn + face.e2, oldVn + face.e0); edges.Add(newEdge0); edges.Add(newEdge1); edges.Add(newEdge2); // 自分は中点3点からなる面に変換 face.e0 = oldEn + (i * 3) + 0; face.e1 = oldEn + (i * 3) + 1; face.e2 = oldEn + (i * 3) + 2; } // edgeの分割 for (int i = 0; i < oldEn; i++) { var edge = edges[i]; // 新しい頂点を使ってedgeを分割 var midVi = oldVn + i; var newEdge = new Edge(midVi, edge.v1); edges.Add(newEdge); // 自分は終点を新しい点に変更 edge.v1 = midVi; } } public void GetArrays( out Vector3[] verticesOut, out Vector3[] normalsOut, out Vector2[] uvsOut, out int[] indicesOut) { verticesOut = new Vector3[vertices.Count]; normalsOut = new Vector3[vertices.Count]; uvsOut = new Vector2[vertices.Count]; for (int i = 0; i < vertices.Count; i++) { verticesOut[i] = vertices[i].position; normalsOut[i] = vertices[i].normal; uvsOut[i] = vertices[i].uv; } indicesOut = MakeIndices(); } public void GetArrays( out Vector3[] verticesOut, out Vector3[] normalsOut, out int[] indicesOut) { Vector2[] uvsUnused; GetArrays(out verticesOut, out normalsOut, out uvsUnused, out indicesOut); } public int[] MakeIndices() { var ret = new int[faces.Count * 3]; // 数は決まっている for (int i = 0; i < faces.Count; i++) // 各面についてインデクスを生成 { int vi0, vi1, vi2; MakeIndices(out vi0, out vi1, out vi2, i); // 法線とのチェック var v0 = vertices[vi0].position; var v1 = vertices[vi1].position; var v2 = vertices[vi2].position; var cross = Vector3.Cross(v1 - v0, v2 - v0); var dp = Vector3.Dot(cross, vertices[vi0].normal); if (dp < 0f) // 法線が合わないので反転 { var tmp = vi1; vi1 = vi2; vi2 = tmp; } ret[(i * 3) + 0] = vi0; ret[(i * 3) + 1] = vi1; ret[(i * 3) + 2] = vi2; } return ret; } void MakeIndices(out int vi0, out int vi1, out int vi2, int fi) { var f = faces[fi]; var e0 = edges[f.e0]; var e1 = edges[f.e1]; var e2 = edges[f.e2]; vi0 = e0.v0; vi1 = e0.v1; if ((e1.v0 != vi0) && (e1.v0 != vi1)) { vi2 = e1.v0; } else { vi2 = e1.v1; } // e2の頂点は両方すでに見つかっているはず Debug.Assert((e2.v0 == vi0) || (e2.v0 == vi1) || (e2.v0 == vi2)); Debug.Assert((e2.v1 == vi0) || (e2.v1 == vi1) || (e2.v1 == vi2)); } // non-public ----------------------- class Edge { public Edge(int v0, int v1) { this.v0 = v0; this.v1 = v1; } public int v0, v1; } class Face { public Face(int e0, int e1, int e2) { this.e0 = e0; this.e1 = e1; this.e2 = e2; } public int e0, e1, e2; } public class Vertex { public Vertex(Vector3 position, Vector3 normal, Vector3 uv) { this.position = position; this.normal = normal; this.uv = uv; } public Vector3 position; public Vector3 normal; public Vector2 uv; } Face MakeDividedFace(int fi, int divIndex, int oldEn, int oldFn, int oldEi0, int oldEi1) { // 新しく面を生成 int ei0 = oldEn + (fi * 3) + divIndex; int ei1, ei2; Edge e0 = edges[oldEi0]; Edge e1 = edges[oldEi1]; if ((e0.v0 == e1.v0) || (e0.v0 == e1.v1)) // e0.v0が頂点 { ei1 = oldEi0; if (e0.v0 == e1.v0) { ei2 = oldEi1; } else { ei2 = oldEn + (oldFn * 3) + oldEi1; } } else if ((e0.v1 == e1.v0) || (e0.v1 == e1.v1)) // e0.v1が頂点 { ei1 = oldEn + (oldFn * 3) + oldEi0; if (e0.v1 == e1.v0) { ei2 = oldEi1; } else { ei2 = oldEn + (oldFn * 3) + oldEi1; } } else // バグ { Debug.Assert(false); ei1 = ei2 = int.MaxValue; // 死ぬべき } var newFace = new Face(ei0, ei1, ei2); return newFace; } uint EdgeKey(int vi0, int vi1) { Debug.Assert(vi0 <= 0xffff); Debug.Assert(vi1 <= 0xffff); if (vi0 > vi1) { var tmp = vi0; vi0 = vi1; vi1 = tmp; } return (uint)((vi0 << 16) | vi1); } List vertices; List edges; List faces; } }