using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; namespace ProceduralToolkit { /// /// Helper class for procedural mesh generation /// [Serializable] public partial class MeshDraft { public string name = ""; public List vertices = new List(); public List triangles = new List(); public List normals = new List(); public List tangents = new List(); public List uv = new List(); public List uv2 = new List(); public List uv3 = new List(); public List uv4 = new List(); public List colors = new List(); /// /// Shortcut for vertices.Count /// public int vertexCount => vertices.Count; /// /// Creates an empty MeshDraft /// public MeshDraft() { } /// /// Creates a new MeshDraft with vertex data from the > /// public MeshDraft(Mesh mesh) { if (mesh == null) throw new ArgumentNullException(nameof(mesh)); name = mesh.name; mesh.GetVertices(vertices); mesh.GetTriangles(triangles, 0); mesh.GetNormals(normals); mesh.GetTangents(tangents); mesh.GetUVs(0, uv); mesh.GetUVs(1, uv2); mesh.GetUVs(2, uv3); mesh.GetUVs(3, uv4); mesh.GetColors(colors); } /// /// Adds vertex data from the /// public MeshDraft Add(MeshDraft draft) { if (draft == null) throw new ArgumentNullException(nameof(draft)); for (var i = 0; i < draft.triangles.Count; i++) { triangles.Add(draft.triangles[i] + vertices.Count); } vertices.AddRange(draft.vertices); normals.AddRange(draft.normals); tangents.AddRange(draft.tangents); uv.AddRange(draft.uv); uv2.AddRange(draft.uv2); uv3.AddRange(draft.uv3); uv4.AddRange(draft.uv4); colors.AddRange(draft.colors); return this; } #region AddTriangle /// /// Adds a triangle to the draft /// public MeshDraft AddTriangle(Vector3 vertex0, Vector3 vertex1, Vector3 vertex2, bool calculateNormal) { if (calculateNormal) { Vector3 normal = Vector3.Cross(vertex1 - vertex0, vertex2 - vertex0).normalized; return AddTriangle(vertex0, vertex1, vertex2, normal, normal, normal); } return _AddTriangle(vertex0, vertex1, vertex2); } /// /// Adds a triangle to the draft /// public MeshDraft AddTriangle(Vector3 vertex0, Vector3 vertex1, Vector3 vertex2, Vector3 normal) { return AddTriangle(vertex0, vertex1, vertex2, normal, normal, normal); } /// /// Adds a triangle to the draft /// public MeshDraft AddTriangle(Vector3 vertex0, Vector3 vertex1, Vector3 vertex2, Vector3 normal0, Vector3 normal1, Vector3 normal2) { normals.Add(normal0); normals.Add(normal1); normals.Add(normal2); return _AddTriangle(vertex0, vertex1, vertex2); } /// /// Adds a triangle to the draft /// public MeshDraft AddTriangle(Vector3 vertex0, Vector3 vertex1, Vector3 vertex2, bool calculateNormal, Vector2 uv0, Vector2 uv1, Vector2 uv2) { uv.Add(uv0); uv.Add(uv1); uv.Add(uv2); return AddTriangle(vertex0, vertex1, vertex2, calculateNormal); } /// /// Adds a triangle to the draft /// public MeshDraft AddTriangle(Vector3 vertex0, Vector3 vertex1, Vector3 vertex2, Vector3 normal, Vector2 uv0, Vector2 uv1, Vector2 uv2) { uv.Add(uv0); uv.Add(uv1); uv.Add(uv2); return AddTriangle(vertex0, vertex1, vertex2, normal, normal, normal); } /// /// Adds a triangle to the draft /// public MeshDraft AddTriangle(Vector3 vertex0, Vector3 vertex1, Vector3 vertex2, Vector3 normal0, Vector3 normal1, Vector3 normal2, Vector2 uv0, Vector2 uv1, Vector2 uv2) { uv.Add(uv0); uv.Add(uv1); uv.Add(uv2); return AddTriangle(vertex0, vertex1, vertex2, normal0, normal1, normal2); } private MeshDraft _AddTriangle(Vector3 vertex0, Vector3 vertex1, Vector3 vertex2) { triangles.Add(0 + vertices.Count); triangles.Add(1 + vertices.Count); triangles.Add(2 + vertices.Count); vertices.Add(vertex0); vertices.Add(vertex1); vertices.Add(vertex2); return this; } #endregion AddTriangle #region AddQuad /// /// Adds a quad to the draft /// public MeshDraft AddQuad(Vector3 origin, Vector3 width, Vector3 height, bool calculateNormal) { Vector3 vertex0 = origin; Vector3 vertex1 = origin + height; Vector3 vertex2 = origin + height + width; Vector3 vertex3 = origin + width; if (calculateNormal) { Vector3 normal = Vector3.Cross(height, width).normalized; return AddQuad(vertex0, vertex1, vertex2, vertex3, normal, normal, normal, normal); } return _AddQuad(vertex0, vertex1, vertex2, vertex3); } /// /// Adds a quad to the draft /// public MeshDraft AddQuad(Vector3 vertex0, Vector3 vertex1, Vector3 vertex2, Vector3 vertex3, bool calculateNormal) { if (calculateNormal) { Vector3 normal = Vector3.Cross(vertex1 - vertex0, vertex3 - vertex0).normalized; // Fix for degenerate triangle-like quads if (normal.sqrMagnitude < Geometry.Epsilon) { normal = Vector3.Cross(vertex3 - vertex2, vertex1 - vertex2).normalized; } return AddQuad(vertex0, vertex1, vertex2, vertex3, normal, normal, normal, normal); } return _AddQuad(vertex0, vertex1, vertex2, vertex3); } /// /// Adds a quad to the draft /// public MeshDraft AddQuad(Vector3 vertex0, Vector3 vertex1, Vector3 vertex2, Vector3 vertex3, Vector3 normal) { return AddQuad(vertex0, vertex1, vertex2, vertex3, normal, normal, normal, normal); } /// /// Adds a quad to the draft /// public MeshDraft AddQuad(Vector3 vertex0, Vector3 vertex1, Vector3 vertex2, Vector3 vertex3, Vector3 normal0, Vector3 normal1, Vector3 normal2, Vector3 normal3) { normals.Add(normal0); normals.Add(normal1); normals.Add(normal2); normals.Add(normal3); return _AddQuad(vertex0, vertex1, vertex2, vertex3); } /// /// Adds a quad to the draft /// public MeshDraft AddQuad(Vector3 origin, Vector3 width, Vector3 height, bool calculateNormal, Vector2 uv0, Vector2 uv1, Vector2 uv2, Vector2 uv3) { uv.Add(uv0); uv.Add(uv1); uv.Add(uv2); uv.Add(uv3); return AddQuad(origin, width, height, calculateNormal); } /// /// Adds a quad to the draft /// public MeshDraft AddQuad(Vector3 vertex0, Vector3 vertex1, Vector3 vertex2, Vector3 vertex3, bool calculateNormal, Vector2 uv0, Vector2 uv1, Vector2 uv2, Vector2 uv3) { uv.Add(uv0); uv.Add(uv1); uv.Add(uv2); uv.Add(uv3); return AddQuad(vertex0, vertex1, vertex2, vertex3, calculateNormal); } /// /// Adds a quad to the draft /// public MeshDraft AddQuad(Vector3 vertex0, Vector3 vertex1, Vector3 vertex2, Vector3 vertex3, Vector3 normal, Vector2 uv0, Vector2 uv1, Vector2 uv2, Vector2 uv3) { uv.Add(uv0); uv.Add(uv1); uv.Add(uv2); uv.Add(uv3); return AddQuad(vertex0, vertex1, vertex2, vertex3, normal, normal, normal, normal); } /// /// Adds a quad to the draft /// public MeshDraft AddQuad(Vector3 vertex0, Vector3 vertex1, Vector3 vertex2, Vector3 vertex3, Vector3 normal0, Vector3 normal1, Vector3 normal2, Vector3 normal3, Vector2 uv0, Vector2 uv1, Vector2 uv2, Vector2 uv3) { uv.Add(uv0); uv.Add(uv1); uv.Add(uv2); uv.Add(uv3); return AddQuad(vertex0, vertex1, vertex2, vertex3, normal0, normal1, normal2, normal3); } private MeshDraft _AddQuad(Vector3 vertex0, Vector3 vertex1, Vector3 vertex2, Vector3 vertex3) { triangles.Add(0 + vertices.Count); triangles.Add(1 + vertices.Count); triangles.Add(2 + vertices.Count); triangles.Add(0 + vertices.Count); triangles.Add(2 + vertices.Count); triangles.Add(3 + vertices.Count); vertices.Add(vertex0); vertices.Add(vertex1); vertices.Add(vertex2); vertices.Add(vertex3); return this; } #endregion AddQuad #region AddTriangleFan /// /// Adds a triangle fan to the draft /// /// /// https://en.wikipedia.org/wiki/Triangle_fan /// public MeshDraft AddTriangleFan(IReadOnlyList fan, bool reverseTriangles = false) { Vector3 normal = Vector3.Cross(fan[1] - fan[0], fan[fan.Count - 1] - fan[0]).normalized; return AddTriangleFan(fan, normal, reverseTriangles); } /// /// Adds a triangle fan to the draft /// /// /// https://en.wikipedia.org/wiki/Triangle_fan /// public MeshDraft AddTriangleFan(IReadOnlyList fan, Vector3 normal, bool reverseTriangles = false) { AddTriangleFanVertices(fan, reverseTriangles); for (int i = 0; i < fan.Count; i++) { normals.Add(normal); } return this; } /// /// Adds a triangle fan to the draft /// /// /// https://en.wikipedia.org/wiki/Triangle_fan /// public MeshDraft AddTriangleFan(IReadOnlyList fan, IReadOnlyList normals, bool reverseTriangles = false) { AddTriangleFanVertices(fan, reverseTriangles); this.normals.AddRange(normals); return this; } /// /// Adds a triangle fan to the draft /// /// /// https://en.wikipedia.org/wiki/Triangle_fan /// public MeshDraft AddTriangleFan(IReadOnlyList fan, IReadOnlyList uv, bool reverseTriangles = false) { this.uv.AddRange(uv); return AddTriangleFan(fan, reverseTriangles); } /// /// Adds a triangle fan to the draft /// /// /// https://en.wikipedia.org/wiki/Triangle_fan /// public MeshDraft AddTriangleFan(IReadOnlyList fan, Vector3 normal, IReadOnlyList uv, bool reverseTriangles = false) { this.uv.AddRange(uv); return AddTriangleFan(fan, normal, reverseTriangles); } /// /// Adds a triangle fan to the draft /// /// /// https://en.wikipedia.org/wiki/Triangle_fan /// public MeshDraft AddTriangleFan(IReadOnlyList fan, IReadOnlyList normals, IReadOnlyList uv, bool reverseTriangles = false) { this.uv.AddRange(uv); return AddTriangleFan(fan, normals, reverseTriangles); } private void AddTriangleFanVertices(IReadOnlyList fan, bool reverseTriangles) { int count = vertices.Count; if (reverseTriangles) { for (int i = fan.Count - 1; i > 1; i--) { triangles.Add(0 + count); triangles.Add(i + count); triangles.Add(i - 1 + count); } } else { for (int i = 1; i < fan.Count - 1; i++) { triangles.Add(0 + count); triangles.Add(i + count); triangles.Add(i + 1 + count); } } vertices.AddRange(fan); } #endregion AddTriangleFan #region AddTriangleStrip /// /// Adds a triangle strip to the draft /// /// /// https://en.wikipedia.org/wiki/Triangle_strip /// public MeshDraft AddTriangleStrip(IReadOnlyList strip) { Vector3 normal = Vector3.Cross(strip[1] - strip[0], strip[2] - strip[0]).normalized; return AddTriangleStrip(strip, normal); } /// /// Adds a triangle strip to the draft /// /// /// https://en.wikipedia.org/wiki/Triangle_strip /// public MeshDraft AddTriangleStrip(IReadOnlyList strip, Vector3 normal) { for (int i = 0, j = 1, k = 2; i < strip.Count - 2; i++, j += i%2*2, k += (i + 1)%2*2) { triangles.Add(i + vertices.Count); triangles.Add(j + vertices.Count); triangles.Add(k + vertices.Count); } vertices.AddRange(strip); for (int i = 0; i < strip.Count; i++) { normals.Add(normal); } return this; } /// /// Adds a triangle strip to the draft /// /// /// https://en.wikipedia.org/wiki/Triangle_strip /// public MeshDraft AddTriangleStrip(IReadOnlyList strip, IReadOnlyList normals) { for (int i = 0, j = 1, k = 2; i < strip.Count - 2; i++, j += i%2*2, k += (i + 1)%2*2) { triangles.Add(i + vertices.Count); triangles.Add(j + vertices.Count); triangles.Add(k + vertices.Count); } vertices.AddRange(strip); this.normals.AddRange(normals); return this; } /// /// Adds a triangle strip to the draft /// /// /// https://en.wikipedia.org/wiki/Triangle_strip /// public MeshDraft AddTriangleStrip(IReadOnlyList strip, IReadOnlyList uv) { this.uv.AddRange(uv); return AddTriangleStrip(strip); } /// /// Adds a triangle strip to the draft /// /// /// https://en.wikipedia.org/wiki/Triangle_strip /// public MeshDraft AddTriangleStrip(IReadOnlyList strip, Vector3 normal, IReadOnlyList uv) { this.uv.AddRange(uv); return AddTriangleStrip(strip, normal); } /// /// Adds a triangle strip to the draft /// /// /// https://en.wikipedia.org/wiki/Triangle_strip /// public MeshDraft AddTriangleStrip(IReadOnlyList strip, IReadOnlyList normals, IReadOnlyList uv) { this.uv.AddRange(uv); return AddTriangleStrip(strip, normals); } #endregion AddTriangleStrip #region AddBaselessPyramid /// /// Adds a baseless pyramid to the draft /// public MeshDraft AddBaselessPyramid(Vector3 apex, IReadOnlyList ring, bool generateUV, bool reverseTriangles = false) { if (generateUV) { var uv00 = new Vector2(0, 0); var uvApex = new Vector2(0.5f, 1); var uv10 = new Vector2(1, 0); if (reverseTriangles) { for (int i = ring.Count - 1; i > 0; i--) { AddTriangle(ring[i - 1], apex, ring[i], true, uv00, uvApex, uv10); } AddTriangle(ring[ring.Count - 1], apex, ring[0], true, uv00, uvApex, uv10); } else { for (var i = 0; i < ring.Count - 1; i++) { AddTriangle(ring[i + 1], apex, ring[i], true, uv00, uvApex, uv10); } AddTriangle(ring[0], apex, ring[ring.Count - 1], true, uv00, uvApex, uv10); } } else { if (reverseTriangles) { for (int i = ring.Count - 1; i > 0; i--) { AddTriangle(ring[i - 1], apex, ring[i], true); } AddTriangle(ring[ring.Count - 1], apex, ring[0], true); } else { for (var i = 0; i < ring.Count - 1; i++) { AddTriangle(ring[i + 1], apex, ring[i], true); } AddTriangle(ring[0], apex, ring[ring.Count - 1], true); } } return this; } #endregion AddBaselessPyramid #region AddFlatTriangleBand /// /// Adds a band made from triangles to the draft /// public MeshDraft AddFlatTriangleBand(IReadOnlyList lowerRing, IReadOnlyList upperRing, bool generateUV) { if (lowerRing.Count != upperRing.Count) { throw new ArgumentException("Array sizes must be equal"); } if (lowerRing.Count < 3) { throw new ArgumentException("Array sizes must be greater than 2"); } Vector2 uv00 = new Vector2(0, 0); Vector2 uvBottomCenter = new Vector2(0.5f, 0); Vector2 uv10 = new Vector2(1, 0); Vector2 uv01 = new Vector2(0, 1); Vector2 uvTopCenter = new Vector2(0.5f, 1); Vector2 uv11 = new Vector2(1, 1); Vector3 lower0, upper0, lower1, upper1; for (int i = 0; i < lowerRing.Count - 1; i++) { lower0 = lowerRing[i]; lower1 = lowerRing[i + 1]; upper0 = upperRing[i]; upper1 = upperRing[i + 1]; if (generateUV) { AddTriangle(lower1, upper0, lower0, true, uv00, uvTopCenter, uv10); AddTriangle(lower1, upper1, upper0, true, uvBottomCenter, uv01, uv11); } else { AddTriangle(lower1, upper0, lower0, true); AddTriangle(lower1, upper1, upper0, true); } } lower0 = lowerRing[lowerRing.Count - 1]; lower1 = lowerRing[0]; upper0 = upperRing[upperRing.Count - 1]; upper1 = upperRing[0]; if (generateUV) { AddTriangle(lower1, upper0, lower0, true, uv00, uvTopCenter, uv10); AddTriangle(lower1, upper1, upper0, true, uvBottomCenter, uv01, uv11); } else { AddTriangle(lower1, upper0, lower0, true); AddTriangle(lower1, upper1, upper0, true); } return this; } #endregion AddFlatTriangleBand #region AddFlatQuadBand /// /// Adds a band made from quads to the draft /// public MeshDraft AddFlatQuadBand(IReadOnlyList lowerRing, IReadOnlyList upperRing, bool generateUV) { if (lowerRing.Count != upperRing.Count) { throw new ArgumentException("Array sizes must be equal"); } if (lowerRing.Count < 3) { throw new ArgumentException("Array sizes must be greater than 2"); } Vector3 lower0, upper0, lower1, upper1; int ringCount = lowerRing.Count; float uvXStep = 1f / ringCount; for (int i = 0; i < ringCount - 1; i++) { lower0 = lowerRing[i]; lower1 = lowerRing[i + 1]; upper0 = upperRing[i]; upper1 = upperRing[i + 1]; if (generateUV) { var uvX0 = i * uvXStep; var uxX1 = (i + 1) * uvXStep; Vector2 uv00 = new Vector2(uvX0, 0); Vector2 uv10 = new Vector2(uxX1, 0); Vector2 uv01 = new Vector2(uvX0, 1); Vector2 uv11 = new Vector2(uxX1, 1); AddQuad(lower1, upper1, upper0, lower0, true, uv00, uv01, uv11, uv10); } else { AddQuad(lower1, upper1, upper0, lower0, true); } } lower0 = lowerRing[ringCount - 1]; lower1 = lowerRing[0]; upper0 = upperRing[ringCount - 1]; upper1 = upperRing[0]; if (generateUV) { var uvX0 = (ringCount - 1) * uvXStep; var uxX1 = 1; Vector2 uv00 = new Vector2(uvX0, 0); Vector2 uv10 = new Vector2(uxX1, 0); Vector2 uv01 = new Vector2(uvX0, 1); Vector2 uv11 = new Vector2(uxX1, 1); AddQuad(lower1, upper1, upper0, lower0, true, uv00, uv01, uv11, uv10); } else { AddQuad(lower1, upper1, upper0, lower0, true); } return this; } #endregion AddFlatQuadBand /// /// Clears all vertex data and all triangle indices /// public void Clear() { vertices.Clear(); triangles.Clear(); normals.Clear(); tangents.Clear(); uv.Clear(); uv2.Clear(); uv3.Clear(); uv4.Clear(); colors.Clear(); } /// /// Moves draft vertices by /// public MeshDraft Move(Vector3 vector) { for (int i = 0; i < vertices.Count; i++) { vertices[i] += vector; } return this; } /// /// Rotates draft vertices by /// public MeshDraft Rotate(Quaternion rotation) { for (int i = 0; i < vertices.Count; i++) { vertices[i] = rotation*vertices[i]; normals[i] = rotation*normals[i]; } return this; } /// /// Scales draft vertices uniformly by /// public MeshDraft Scale(float scale) { for (int i = 0; i < vertices.Count; i++) { vertices[i] *= scale; } return this; } /// /// Scales draft vertices non-uniformly by /// public MeshDraft Scale(Vector3 scale) { for (int i = 0; i < vertices.Count; i++) { vertices[i] = Vector3.Scale(vertices[i], scale); normals[i] = Vector3.Scale(normals[i], scale).normalized; } return this; } /// /// Paints draft vertices with /// public MeshDraft Paint(Color color) { colors.Clear(); for (int i = 0; i < vertices.Count; i++) { colors.Add(color); } return this; } /// /// Flips draft faces /// public MeshDraft FlipFaces() { FlipTriangles(); FlipNormals(); return this; } /// /// Reverses the winding order of draft triangles /// public MeshDraft FlipTriangles() { for (int i = 0; i < triangles.Count; i += 3) { var temp = triangles[i]; triangles[i] = triangles[i + 1]; triangles[i + 1] = temp; } return this; } /// /// Reverses the direction of draft normals /// public MeshDraft FlipNormals() { for (int i = 0; i < normals.Count; i++) { normals[i] = -normals[i]; } return this; } /// /// Flips the UV map horizontally in the selected /// public MeshDraft FlipUVHorizontally(int channel = 0) { List list; switch (channel) { case 0: list = uv; break; case 1: list = uv2; break; case 2: list = uv3; break; case 3: list = uv4; break; default: throw new ArgumentOutOfRangeException(nameof(channel)); } for (var i = 0; i < list.Count; i++) { list[i] = new Vector2(1 - list[i].x, list[i].y); } return this; } /// /// Flips the UV map vertically in the selected /// public MeshDraft FlipUVVertically(int channel = 0) { List list; switch (channel) { case 0: list = uv; break; case 1: list = uv2; break; case 2: list = uv3; break; case 3: list = uv4; break; default: throw new ArgumentOutOfRangeException(nameof(channel)); } for (var i = 0; i < list.Count; i++) { list[i] = new Vector2(list[i].x, 1 - list[i].y); } return this; } /// /// Projects vertices on a sphere with the given and , recalculates normals /// public MeshDraft Spherify(float radius, Vector3 center = default) { for (var i = 0; i < vertices.Count; i++) { normals[i] = (vertices[i] - center).normalized; vertices[i] = normals[i]*radius; } return this; } /// /// Creates a new mesh from the data in the draft /// /// Calculate the bounding box of the Mesh after setting the triangles. /// Use 16 bit or 32 bit index buffers based on vertex count. public Mesh ToMesh(bool calculateBounds = true, bool autoIndexFormat = true) { var mesh = new Mesh(); FillMesh(mesh, calculateBounds, autoIndexFormat); return mesh; } /// /// Fills the with the data in the draft /// /// Resulting mesh. Cleared before use. /// Calculate the bounding box of the Mesh after setting the triangles. /// Use 16 bit or 32 bit index buffers based on vertex count. public void ToMesh(Mesh mesh, bool calculateBounds = true, bool autoIndexFormat = true) { if (mesh == null) { throw new ArgumentNullException(nameof(mesh)); } mesh.Clear(false); FillMesh(mesh, calculateBounds, autoIndexFormat); } private void FillMesh(Mesh mesh, bool calculateBounds, bool autoIndexFormat) { if (vertexCount > 65535) { if (autoIndexFormat) { mesh.indexFormat = IndexFormat.UInt32; } else { Debug.LogError("A mesh can't have more than 65535 vertices with 16 bit index buffer. Vertex count: " + vertexCount); mesh.indexFormat = IndexFormat.UInt16; } } else { if (autoIndexFormat) { mesh.indexFormat = IndexFormat.UInt16; } } mesh.name = name; mesh.SetVertices(vertices); mesh.SetTriangles(triangles, 0, calculateBounds); mesh.SetNormals(normals); mesh.SetTangents(tangents); mesh.SetUVs(0, uv); mesh.SetUVs(1, uv2); mesh.SetUVs(2, uv3); mesh.SetUVs(3, uv4); mesh.SetColors(colors); } public override string ToString() { return name + " (ProceduralToolkit.MeshDraft)"; } } }