1078 lines
39 KiB
C#
1078 lines
39 KiB
C#
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<Vector2> 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<Vector2> 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<Vector2> 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<Vector2> 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<Vector3> verticesIn,
|
|
IList<Vector2> uvsIn,
|
|
IList<int> 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<Vector3> verticesIn,
|
|
IList<int> 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<Vector3> positions,
|
|
IList<Vector3> normals,
|
|
IList<Vector2> uvs,
|
|
IList<int> indices)
|
|
{
|
|
vertices = new List<Vertex>();
|
|
edges = new List<Edge>();
|
|
faces = new List<Face>();
|
|
|
|
// まず頂点充填
|
|
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<uint>();
|
|
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<uint, int>();
|
|
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<Vertex> 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<Vertex> vertices;
|
|
List<Edge> edges;
|
|
List<Face> faces;
|
|
}
|
|
} |