Car/Assets/VolumetricLightBeam/Scripts/MeshGenerator.cs

445 lines
17 KiB
C#
Raw Normal View History

using UnityEngine;
namespace VLB
{
public static class MeshGenerator
{
const float kMinTruncatedRadius = 0.001f;
static float GetAngleOffset(int numSides)
{
// rotate square beams so they are properly oriented to scale them more easily
return numSides == 4 ? (Mathf.PI * 0.25f) : 0f;
}
static float GetRadiiScale(int numSides)
{
// with 4 sides, place the vertices in the square's corners to fit with the depth buffer camera frustum
return numSides == 4 ? Mathf.Sqrt(2f) : 1f;
}
public static Mesh GenerateConeZ_RadiusAndAngle(float lengthZ, float radiusStart, float coneAngle, int numSides, int numSegments, bool cap, bool doubleSided)
{
Debug.Assert(lengthZ > 0f);
Debug.Assert(coneAngle > 0f && coneAngle < 180f);
var radiusEnd = lengthZ * Mathf.Tan(coneAngle * Mathf.Deg2Rad * 0.5f);
return GenerateConeZ_Radii(lengthZ, radiusStart, radiusEnd, numSides, numSegments, cap, doubleSided);
}
public static Mesh GenerateConeZ_Angle(float lengthZ, float coneAngle, int numSides, int numSegments, bool cap, bool doubleSided)
{
return GenerateConeZ_RadiusAndAngle(lengthZ, 0f, coneAngle, numSides, numSegments, cap, doubleSided);
}
// Cone with optional cap (at start only), optional segments count, vertices + UVs (storing info about cap and side)
public static Mesh GenerateConeZ_Radii(float lengthZ, float radiusStart, float radiusEnd, int numSides, int numSegments, bool cap, bool doubleSided)
{
Debug.Assert(lengthZ > 0f);
Debug.Assert(radiusStart >= 0f);
Debug.Assert(numSides >= 3);
Debug.Assert(numSegments >= 0);
var mesh = new Mesh();
bool genCap = cap && radiusStart > 0f;
// We use the XY position of the vertices to compute the cone normal in the shader.
// With a perfectly sharp cone, we couldn't compute accurate normals at its top.
radiusStart = Mathf.Max(radiusStart, kMinTruncatedRadius);
{
float radiiScale = GetRadiiScale(numSides);
radiusStart *= radiiScale;
radiusEnd *= radiiScale;
}
int vertCountSides = numSides * (numSegments + 2);
int vertCountTotal = vertCountSides;
if (genCap)
vertCountTotal += numSides + 1;
// VERTICES
{
float angleOffset = GetAngleOffset(numSides);
var vertices = new Vector3[vertCountTotal];
for (int i = 0; i < numSides; i++)
{
float angle = angleOffset + 2 * Mathf.PI * i / numSides;
float angleCos = Mathf.Cos(angle);
float angleSin = Mathf.Sin(angle);
for (int seg = 0; seg < numSegments + 2; seg++)
{
float tseg = (float)seg / (numSegments + 1);
Debug.Assert(tseg >= 0f && tseg <= 1f);
float radius = Mathf.Lerp(radiusStart, radiusEnd, tseg);
vertices[i + seg * numSides] = new Vector3(radius * angleCos, radius * angleSin, tseg * lengthZ);
}
}
if (genCap)
{
int ind = vertCountSides;
vertices[ind] = Vector3.zero;
ind++;
for (int i = 0; i < numSides; i++)
{
float angle = angleOffset + 2 * Mathf.PI * i / numSides;
float angleCos = Mathf.Cos(angle);
float angleSin = Mathf.Sin(angle);
vertices[ind] = new Vector3(radiusStart * angleCos, radiusStart * angleSin, 0f);
ind++;
}
Debug.Assert(ind == vertices.Length);
}
if (!doubleSided)
{
mesh.vertices = vertices;
}
else
{
var vertices2 = new Vector3[vertices.Length * 2];
vertices.CopyTo(vertices2, 0);
vertices.CopyTo(vertices2, vertices.Length);
mesh.vertices = vertices2;
}
}
// UV (used to flags vertices as sides or cap)
// X: 0 = sides ; 1 = cap
// Y: 0 = front face ; 1 = back face (doubleSided only)
{
var uv = new Vector2[vertCountTotal];
int ind = 0;
for (int i = 0; i < vertCountSides; i++)
uv[ind++] = Vector2.zero;
if (genCap)
{
for (int i = 0; i < numSides + 1; i++)
uv[ind++] = new Vector2(1, 0);
}
Debug.Assert(ind == uv.Length);
if (!doubleSided)
{
mesh.uv = uv;
}
else
{
var uv2 = new Vector2[uv.Length * 2];
uv.CopyTo(uv2, 0);
uv.CopyTo(uv2, uv.Length);
for (int i = 0; i < uv.Length; i++)
{
var value = uv2[i + uv.Length];
uv2[i + uv.Length] = new Vector2(value.x, 1);
}
mesh.uv = uv2;
}
}
// INDICES
{
int triCountSides = numSides * 2 * Mathf.Max(numSegments + 1, 1);
int indCountSides = triCountSides * 3;
int indCountTotal = indCountSides;
if (genCap)
indCountTotal += numSides * 3;
var indices = new int[indCountTotal];
int ind = 0;
for (int i = 0; i < numSides; i++)
{
int ip1 = i + 1;
if (ip1 == numSides)
ip1 = 0;
for (int k = 0; k < numSegments + 1; ++k)
{
var offset = k * numSides;
indices[ind++] = offset + i;
indices[ind++] = offset + ip1;
indices[ind++] = offset + i + numSides;
indices[ind++] = offset + ip1 + numSides;
indices[ind++] = offset + i + numSides;
indices[ind++] = offset + ip1;
}
}
if (genCap)
{
for (int i = 0; i < numSides - 1; i++)
{
indices[ind++] = vertCountSides;
indices[ind++] = vertCountSides + i + 2;
indices[ind++] = vertCountSides + i + 1;
}
indices[ind++] = vertCountSides;
indices[ind++] = vertCountSides + 1;
indices[ind++] = vertCountSides + numSides;
}
Debug.Assert(ind == indices.Length);
if (!doubleSided)
{
mesh.triangles = indices;
}
else
{
var indices2 = new int[indices.Length * 2];
indices.CopyTo(indices2, 0);
for (int i = 0; i < indices.Length; i += 3)
{
indices2[indices.Length + i + 0] = indices[i + 0] + vertCountTotal;
indices2[indices.Length + i + 1] = indices[i + 2] + vertCountTotal;
indices2[indices.Length + i + 2] = indices[i + 1] + vertCountTotal;
}
mesh.triangles = indices2;
}
}
mesh.bounds = ComputeBounds(lengthZ, radiusStart, radiusEnd);
Debug.Assert(mesh.vertexCount == GetVertexCount(numSides, numSegments, genCap ? CapMode.SpecificVerticesPerCap_1Cap : CapMode.None, doubleSided));
Debug.Assert(mesh.triangles.Length == GetIndicesCount(numSides, numSegments, genCap ? CapMode.SpecificVerticesPerCap_1Cap : CapMode.None, doubleSided));
return mesh;
}
// Cone with double caps, no segments, vertices only (no UVs)
public static Mesh GenerateConeZ_Radii_DoubleCaps(float lengthZ, float radiusStart, float radiusEnd, int numSides, bool inverted)
{
Debug.Assert(lengthZ > 0f);
Debug.Assert(radiusStart >= 0f);
Debug.Assert(numSides >= 3);
var mesh = new Mesh();
// We use the XY position of the vertices to compute the cone normal in the shader.
// With a perfectly sharp cone, we couldn't compute accurate normals at its top.
radiusStart = Mathf.Max(radiusStart, kMinTruncatedRadius);
int vertCountSides = numSides * 2;
int vertCountTotal = vertCountSides;
System.Func<int, int> vertSidesStartFromSlide = (int slideID) =>
{
return numSides * slideID;
};
System.Func<int, int> vertCenterFromSlide = (int slideID) =>
{
return vertCountSides + slideID;
};
vertCountTotal += 2; // caps
// VERTICES
{
float angleOffset = GetAngleOffset(numSides);
var vertices = new Vector3[vertCountTotal];
for (int i = 0; i < numSides; i++)
{
float angle = angleOffset + 2 * Mathf.PI * i / numSides;
float angleCos = Mathf.Cos(angle);
float angleSin = Mathf.Sin(angle);
for (int seg = 0; seg < 2; seg++)
{
float tseg = (float)seg;
Debug.Assert(tseg >= 0f && tseg <= 1f);
float radius = Mathf.Lerp(radiusStart, radiusEnd, tseg);
vertices[i + vertSidesStartFromSlide(seg)] = new Vector3(radius * angleCos, radius * angleSin, tseg * lengthZ);
}
}
vertices[vertCenterFromSlide(0)] = Vector3.zero; // cap start
vertices[vertCenterFromSlide(1)] = new Vector3(0f, 0f, lengthZ); // cap end
mesh.vertices = vertices;
}
// INDICES
{
int triCountSides = numSides * 2;
int indCountSides = triCountSides * 3;
int indCountTotal = indCountSides;
indCountTotal += numSides * 3; // cap start
indCountTotal += numSides * 3; // cap end
var indices = new int[indCountTotal];
int ind = 0;
for (int i = 0; i < numSides; i++)
{
int ip1 = i + 1;
if (ip1 == numSides)
ip1 = 0;
for (int k = 0; k < 1; ++k)
{
var offset = k * numSides;
indices[ind + 0] = offset + i;
indices[ind + (inverted ? 1 : 2)] = offset + ip1;
indices[ind + (inverted ? 2 : 1)] = offset + i + numSides;
indices[ind + 3] = offset + ip1 + numSides;
indices[ind + (inverted ? 4 : 5)] = offset + i + numSides;
indices[ind + (inverted ? 5 : 4)] = offset + ip1;
ind += 6;
}
}
System.Action<int, bool> generateCapIndexes = (int slideID, bool invert) =>
{
int vertSidesStart = vertSidesStartFromSlide(slideID);
for (int i = 0; i < numSides - 1; i++)
{
indices[ind + 0] = vertCenterFromSlide(slideID);
indices[ind + (invert ? 1 : 2)] = vertSidesStart + i + 1;
indices[ind + (invert ? 2 : 1)] = vertSidesStart + i + 0;
ind += 3;
}
indices[ind + 0] = vertCenterFromSlide(slideID);
indices[ind + (invert ? 1 : 2)] = vertSidesStart;
indices[ind + (invert ? 2 : 1)] = vertSidesStart + numSides - 1;
ind += 3;
};
generateCapIndexes(0, inverted); // cap start
generateCapIndexes(1, !inverted); // cap end
Debug.Assert(ind == indices.Length);
mesh.triangles = indices;
}
var bounds = new Bounds(
new Vector3(0, 0, lengthZ * 0.5f),
new Vector3(Mathf.Max(radiusStart, radiusEnd) * 2, Mathf.Max(radiusStart, radiusEnd) * 2, lengthZ)
);
mesh.bounds = bounds;
Debug.Assert(mesh.vertexCount == GetVertexCount(numSides, 0, CapMode.OneVertexPerCap_2Caps, doubleSided: false));
Debug.Assert(mesh.triangles.Length == GetIndicesCount(numSides, 0, CapMode.OneVertexPerCap_2Caps, doubleSided: false));
return mesh;
}
public static Bounds ComputeBounds(float lengthZ, float radiusStart, float radiusEnd)
{
float maxDiameter = Mathf.Max(radiusStart, radiusEnd) * 2;
return new Bounds(
new Vector3(0, 0, lengthZ * 0.5f),
new Vector3(maxDiameter, maxDiameter, lengthZ)
);
}
public enum CapMode
{
None,
OneVertexPerCap_1Cap,
OneVertexPerCap_2Caps,
SpecificVerticesPerCap_1Cap,
SpecificVerticesPerCap_2Caps,
}
static int GetCapAdditionalVerticesCount(CapMode capMode, int numSides)
{
switch (capMode)
{
case CapMode.None: return 0;
case CapMode.OneVertexPerCap_1Cap: return 1;
case CapMode.OneVertexPerCap_2Caps: return 2;
case CapMode.SpecificVerticesPerCap_1Cap: return 1 * (numSides + 1);
case CapMode.SpecificVerticesPerCap_2Caps: return 2 * (numSides + 1);
default: return 0;
}
}
static int GetCapAdditionalIndicesCount(CapMode capMode, int numSides)
{
switch (capMode)
{
case CapMode.None: return 0;
case CapMode.OneVertexPerCap_1Cap:
case CapMode.SpecificVerticesPerCap_1Cap: return 1 * (numSides * 3);
case CapMode.OneVertexPerCap_2Caps:
case CapMode.SpecificVerticesPerCap_2Caps: return 2 * (numSides * 3);
default: return 0;
}
}
public static int GetVertexCount(int numSides, int numSegments, CapMode capMode, bool doubleSided)
{
Debug.Assert(numSides >= 2);
Debug.Assert(numSegments >= 0);
int count = numSides * (numSegments + 2);
count += GetCapAdditionalVerticesCount(capMode, numSides);
if (doubleSided) count *= 2;
return count;
}
public static int GetIndicesCount(int numSides, int numSegments, CapMode capMode, bool doubleSided)
{
Debug.Assert(numSides >= 2);
Debug.Assert(numSegments >= 0);
int count = numSides * (numSegments + 1) * 2 * 3;
count += GetCapAdditionalIndicesCount(capMode, numSides);
if (doubleSided) count *= 2;
return count;
}
public static int GetSharedMeshVertexCount()
{
return GetVertexCount(Config.Instance.sharedMeshSides, Config.Instance.sharedMeshSegments, CapMode.SpecificVerticesPerCap_1Cap, Config.Instance.SD_requiresDoubleSidedMesh);
}
public static int GetSharedMeshIndicesCount()
{
return GetIndicesCount(Config.Instance.sharedMeshSides, Config.Instance.sharedMeshSegments, CapMode.SpecificVerticesPerCap_1Cap, Config.Instance.SD_requiresDoubleSidedMesh);
}
public static int GetSharedMeshHDVertexCount()
{
return GetVertexCount(Config.Instance.sharedMeshSides, 0, CapMode.OneVertexPerCap_2Caps, doubleSided: false);
}
public static int GetSharedMeshHDIndicesCount()
{
return GetIndicesCount(Config.Instance.sharedMeshSides, 0, CapMode.OneVertexPerCap_2Caps, doubleSided: false);
}
}
}