using System; using System.Collections.Generic; using Unity.Collections; using Unity.Mathematics; using UnityEngine.Rendering; using UnityEngine.Splines.ExtrusionShapes; namespace UnityEngine.Splines { /// /// Contains settings pertaining to the creation of mesh geometry for an /// extruded shape. Use with . /// /// A type implementing . public struct ExtrudeSettings where T : IExtrudeShape { const int k_SegmentsMin = 2, k_SegmentsMax = 4096; const float k_RadiusMin = .00001f, k_RadiusMax = 10000f; [SerializeField] T m_Shape; [SerializeField] bool m_CapEnds; [SerializeField] bool m_FlipNormals; [SerializeField] int m_SegmentCount; [SerializeField] float m_Radius; [SerializeField] Vector2 m_Range; /// /// How many sections compose the length of the mesh. /// public int SegmentCount { get => m_SegmentCount; set => m_SegmentCount = value; } /// /// Whether the start and end of the mesh is filled. This setting is ignored /// when the extruded spline is closed. /// Important note - cap are triangulated using a method that assumes convex geometry. /// If the input shape is concave, caps may show visual artifacts or overlaps. /// public bool CapEnds { get => m_CapEnds; set => m_CapEnds = value; } /// /// Set true to reverse the winding order of vertices so that the face normals are inverted. This is useful /// primarily for templates where the input path may not produce a counter-clockwise /// vertex ring. Counter-clockwise winding equates to triangles facing outwards. /// public bool FlipNormals { get => m_FlipNormals; set => m_FlipNormals = value; } /// /// The section of the Spline to extrude. This value expects a normalized interpolation start and end. /// I.e., [0,1] is the entire Spline, whereas [.5, 1] is the last half of the Spline. /// public float2 Range { get => m_Range; set => m_Range = math.clamp(new float2(math.min(value.x, value.y), math.max(value.x, value.y)), 0f, 1f); } /// /// The radius of the extruded mesh. Radius is half of the width of the entire shape. /// The return value of is multiplied by this /// value to determine the size of the resulting shape. /// public float Radius { get => m_Radius; set => m_Radius = math.clamp(value, k_RadiusMin, k_RadiusMax); } /// /// The object defines the outline path of each segment. /// public T Shape { get => m_Shape; set => m_Shape = value; } internal bool DoCapEnds(K spline) where K : ISpline => m_CapEnds && !spline.Closed; internal bool DoCloseSpline(K spline) where K : ISpline => math.abs(1f - (Range.y - Range.x)) < float.Epsilon && spline.Closed; internal int sides { get { if (Shape is SplineShape) return wrapped ? Shape.SideCount + 1 : Shape.SideCount; return wrapped ? Shape.SideCount : Shape.SideCount + 1; } } // true if a revolution ends at the start vertex, or false if it ends at the last vertex in the ring internal bool wrapped { get { if (Shape is SplineShape splineShape) { if (splineShape.Spline != null) return splineShape.Spline.Closed; } if (Shape is Road) return false; return true; } } /// /// Create a new settings object with an /// instance. A default set of parameters will be used. /// /// The template to /// be used as the shape template when extruding. public ExtrudeSettings(T shape) : this(16, false, new float2(0, 1), .5f, shape) { } /// /// Create a new settings object. This is used by functions in /// to extrude a shape template along a spline. /// /// The number of segments to divide the extruded spline into when creating vertex rings. /// Defines whether the ends of the extruded spline mesh should be closed. /// The start and end points as normalized interpolation values. /// Defines the size of the extruded mesh. /// The template to /// be used as the shape template when extruding. public ExtrudeSettings(int segments, bool capped, float2 range, float radius, T shape) { m_SegmentCount = math.clamp(segments, k_SegmentsMin, k_SegmentsMax); m_FlipNormals = false; m_Range = math.clamp(new float2(math.min(range.x, range.y), math.max(range.x, range.y)), 0f, 1f); m_CapEnds = capped; m_Radius = math.clamp(radius, k_RadiusMin, k_RadiusMax); m_Shape = shape; } } /// /// Static functions for extruding meshes along paths. /// Use this class to build extruded meshes from code, or /// for a pre-built Component that can be attached to any GameObject with /// a . /// public static class SplineMesh { const int k_SidesMin = 2, k_SidesMax = 2084; static readonly VertexAttributeDescriptor[] k_PipeVertexAttribs = new VertexAttributeDescriptor[] { new (VertexAttribute.Position), new (VertexAttribute.Normal), new (VertexAttribute.TexCoord0, dimension: 2) }; static readonly Circle s_DefaultShape = new Circle(); internal static bool s_IsConvex; static bool s_IsConvexComputed; /// /// Interface for Spline mesh vertex data. Implement this interface if you are extruding custom mesh data and /// do not want to use the vertex layout provided by ."/>. /// public interface ISplineVertexData { /// /// Vertex position. /// public Vector3 position { get; set; } /// /// Vertex normal. /// public Vector3 normal { get; set; } /// /// Vertex texture, corresponds to UV0. /// public Vector2 texture { get; set; } } [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] struct VertexData : ISplineVertexData { public Vector3 position { get; set; } public Vector3 normal { get; set; } public Vector2 texture { get; set; } } static void ExtrudeRing( TSpline spline, ExtrudeSettings settings, int segment, NativeArray data, int start, bool uvsAreCaps = false) where TSpline : ISpline where TShape : IExtrudeShape where TVertex : struct, ISplineVertexData { TShape shape = settings.Shape; int sideCount = settings.sides; float radius = settings.Radius; bool wrap = settings.wrapped; float t = math.lerp(settings.Range.x, settings.Range.y, segment / (settings.SegmentCount - 1f)); var evaluationT = spline.Closed ? math.frac(t) : math.clamp(t, 0f, 1f); spline.Evaluate(evaluationT, out var sp, out var st, out var up); var tangentLength = math.lengthsq(st); if (tangentLength == 0f || float.IsNaN(tangentLength)) { var adjustedT = math.clamp(evaluationT + (0.0001f * (t < 1f ? 1f : -1f)), 0f, 1f); spline.Evaluate(adjustedT, out _, out st, out up); } st = math.normalize(st); var rot = quaternion.LookRotationSafe(st, up); shape.SetSegment(segment, t, sp, st, up); var flip = settings.FlipNormals; for (int n = 0; n < sideCount; ++n) { var vertex = new TVertex(); int index = flip ? sideCount - n - 1 : n; float v = index / (sideCount - 1f); var p = shape.GetPosition(v, index) * radius; vertex.position = sp + math.rotate(rot, new float3(p, 0f)); vertex.normal = (vertex.position - (Vector3)sp).normalized * (flip ? -1f : 1f); // first branch is a special case for wrapping uvs at the caps if (uvsAreCaps) { // the division by 2 is just a guess at a decent default for matching the uvs around the // circumference of extruded shapes. the more accurate solution would be calculate the actual // circumference and use that value to set cap scale. vertex.texture = (p.xy / radius) / 2; } else if (wrap) { // instead of inserting a vertex seam wrap UVs using a triangle wave so that // texture wraps back onto itself float ut = index / ((float)sideCount + sideCount % 2); float u = math.abs(ut - math.floor(ut + 0.5f)) * 2f; vertex.texture = new Vector2(1f - u, t * spline.GetLength()); } else { vertex.texture = new Vector2(1 - index / (sideCount - 1f), t * spline.GetLength()); } data[start + n] = vertex; } if (s_IsConvexComputed) return; ComputeIsConvex(data, st, start, sideCount); } static void ComputeIsConvex( NativeArray data, float3 normal, int start, int sideCount) where TVertex : struct, ISplineVertexData { s_IsConvexComputed = true; bool isNegative = false; bool isPositive = false; for (int n = 0; n < sideCount; ++n) { var indexA = start + n; var indexB = (indexA + 1) % (sideCount - 1); var indexC = (indexB + 1) % (sideCount - 1); var vertexA = data[indexA].position; var vertexB = data[indexB].position; var vertexC = data[indexC].position; var vectorAB = vertexB - vertexA; var vectorBC = vertexC - vertexB; var cross = math.cross(vectorAB, vectorBC); var crossNormalized = math.normalizesafe(cross); var dot = math.dot(normal, crossNormalized); if (dot < 0) isNegative = true; else if (dot > 0) isPositive = true; if (isNegative && isPositive) { s_IsConvex = false; return; } } s_IsConvex = true; } /// /// Calculate the vertex and index count required for an extruded mesh. /// Use this method to allocate attribute and index buffers for use with Extrude. /// /// Whether the extruded vertex ring is open (has a start and end point) or closed (forms an unbroken loop). /// The number of vertices required for an extruded mesh using the provided settings. /// The number of indices required for an extruded mesh using the provided settings. /// How many sides make up the radius of the mesh. /// How many sections compose the length of the mesh. /// Whether the start and end of the mesh is filled. This setting is ignored when spline is closed. /// Whether the extruded mesh is closed or open. This can be separate from the Spline.Closed value. /// Returns true if the computed vertex count exceeds 3 and the computed index count exceeds 5. // ReSharper disable once MemberCanBePrivate.Global public static bool GetVertexAndIndexCount(int sides, int segments, bool capped, bool closed, bool closeRing, out int vertexCount, out int indexCount) { vertexCount = sides * (segments + (capped ? 2 : 0)); indexCount = (closeRing ? sides : sides - 1) * 6 * (segments - (closed ? 0 : 1)) + (capped ? (sides - 2) * 3 * 2 : 0); // make sure we at least have enough vertices and indices for a quad return vertexCount > 3 && indexCount > 5; } /// /// Calculate the vertex and index count required for an extruded mesh. /// Use this method to allocate attribute and index buffers for use with Extrude. /// /// The number of vertices required for an extruded mesh using the provided settings. /// The number of indices required for an extruded mesh using the provided settings. /// How many sides make up the radius of the mesh. /// How many sections compose the length of the mesh. /// /// The section of the Spline to extrude. This value expects a normalized interpolation start and end. /// I.e., [0,1] is the entire Spline, whereas [.5, 1] is the last half of the Spline. /// /// Whether the start and end of the mesh is filled. This setting is ignored when spline is closed. /// Whether the extruded mesh is closed or open. This can be separate from the Spline.Closed value. public static void GetVertexAndIndexCount( int sides, int segments, bool capped, bool closed, Vector2 range, out int vertexCount, out int indexCount) { GetVertexAndIndexCount(sides, segments, capped, closed, true, out vertexCount, out indexCount); } static bool GetVertexAndIndexCount(T spline, ExtrudeSettings settings, out int vertexCount, out int indexCount) where T : ISpline where K : IExtrudeShape { return GetVertexAndIndexCount(settings.sides, settings.SegmentCount, settings.DoCapEnds(spline), settings.DoCloseSpline(spline), settings.wrapped, out vertexCount, out indexCount); } /// /// Extrude a mesh along a spline in a tube-like shape. /// /// The spline to extrude. /// A mesh that will be cleared and filled with vertex data for the shape. /// The radius of the extruded mesh. /// How many sides make up the radius of the mesh. /// How many sections compose the length of the mesh. /// Whether the start and end of the mesh is filled. This setting is ignored when spline is closed. /// A type implementing ISpline. public static void Extrude(T spline, Mesh mesh, float radius, int sides, int segments, bool capped = true) where T : ISpline { Extrude(spline, mesh, radius, sides, segments, capped, new float2(0f, 1f)); } /// /// Extrude a mesh along a spline with a customised shape. /// /// The spline to extrude. /// A mesh that will be cleared and filled with vertex data for the shape. /// The radius of the extruded mesh. /// How many sections compose the length of the mesh. /// Whether the start and end of the mesh is filled. This setting is ignored when spline is closed. /// The object defines the outline path of each segment. /// A type implementing ISpline. /// A type implementing . public static void Extrude(T spline, Mesh mesh, float radius, int segments, bool capped, K shape) where T : ISpline where K : IExtrudeShape { Extrude(spline, mesh, radius, segments, capped, new float2(0f, 1f), shape); } /// /// Extrude a mesh along a spline in a tube-like shape. /// /// The spline to extrude. /// A mesh that will be cleared and filled with vertex data for the shape. /// The radius of the extruded mesh. /// How many sides make up the radius of the mesh. /// How many sections compose the length of the mesh. /// Whether the start and end of the mesh is filled. This setting is ignored when spline is closed. /// /// The section of the Spline to extrude. This value expects a normalized interpolation start and end. /// I.e., [0,1] is the entire Spline, whereas [.5, 1] is the last half of the Spline. /// /// A type implementing ISpline. public static void Extrude(T spline, Mesh mesh, float radius, int sides, int segments, bool capped, float2 range) where T : ISpline { s_DefaultShape.SideCount = sides; Extrude(spline, mesh, radius, segments, capped, range, s_DefaultShape); } /// /// Extrude a mesh along a spline following a shape template. /// /// The spline to extrude. /// A mesh that will be cleared and filled with vertex data for the shape. /// The radius of the extruded mesh. /// How many sections compose the length of the mesh. /// Whether the start and end of the mesh is filled. This setting is ignored when spline is closed. /// /// The section of the Spline to extrude. This value expects a normalized interpolation start and end. /// I.e., [0,1] is the entire Spline, whereas [.5, 1] is the last half of the Spline. /// /// The object defines the outline path of each segment. /// A type implementing ISpline. /// A type implementing . public static void Extrude(T spline, Mesh mesh, float radius, int segments, bool capped, float2 range, K shape) where T : ISpline where K : IExtrudeShape { var settings = new ExtrudeSettings() { Radius = radius, CapEnds = capped, Range = range, SegmentCount = segments, Shape = shape }; Extrude(spline, mesh, settings); } /// /// Extrude a mesh along a spline following a shape template. /// /// The spline to extrude. /// A mesh that will be cleared and filled with vertex data for the shape. /// The parameters used when creating mesh geometry. /// A type implementing ISpline. /// A type implementing . /// Returns true if mesh was created, or false if the settings configuration resulted in an invalid state (ex, too few vertices). public static bool Extrude(T spline, Mesh mesh, ExtrudeSettings settings) where T : ISpline where K : IExtrudeShape { if (!GetVertexAndIndexCount(spline, settings, out var vertexCount, out var indexCount)) return false; var meshDataArray = Mesh.AllocateWritableMeshData(1); var data = meshDataArray[0]; var indexFormat = vertexCount >= ushort.MaxValue ? IndexFormat.UInt32 : IndexFormat.UInt16; data.SetIndexBufferParams(indexCount, indexFormat); data.SetVertexBufferParams(vertexCount, k_PipeVertexAttribs); var vertices = data.GetVertexData(); if (indexFormat == IndexFormat.UInt16) { var indices = data.GetIndexData(); Extrude(spline, vertices, indices, settings); } else { var indices = data.GetIndexData(); Extrude(spline, vertices, indices, settings); } mesh.Clear(); data.subMeshCount = 1; data.SetSubMesh(0, new SubMeshDescriptor(0, indexCount)); Mesh.ApplyAndDisposeWritableMeshData(meshDataArray, mesh); mesh.RecalculateBounds(); return true; } /// /// Extrude a mesh along a list of splines in a tube-like shape. /// /// The splines to extrude. /// A mesh that will be cleared and filled with vertex data for the shape. /// The radius of the extruded mesh. /// How many sides make up the radius of the mesh. /// The number of edge loops that comprise the length of one unit of the mesh. /// Whether the start and end of the mesh is filled. This setting is ignored when spline is closed. /// /// The section of the Spline to extrude. This value expects a normalized interpolation start and end. /// I.e., [0,1] is the entire Spline, whereas [.5, 1] is the last half of the Spline. /// /// A type implementing ISpline. public static void Extrude(IReadOnlyList splines, Mesh mesh, float radius, int sides, float segmentsPerUnit, bool capped, float2 range) where T : ISpline { s_DefaultShape.SideCount = sides; var settings = new ExtrudeSettings(s_DefaultShape) { Radius = radius, SegmentCount = (int) segmentsPerUnit, CapEnds = capped, Range = range }; Extrude(splines, mesh, settings, segmentsPerUnit); } // this is not public for good reason. it mutates the settings object by necessity, to preserve the behaviour // of `segmentsPerUnit` rather than use the `Settings.SegmentCount` property. internal static void Extrude(IReadOnlyList splines, Mesh mesh, ExtrudeSettings settings, float segmentsPerUnit) where T : ISpline where K : IExtrudeShape { mesh.Clear(); if (splines == null) { if(Application.isPlaying) Debug.LogError("Trying to extrude a spline mesh with no valid splines."); return; } var meshDataArray = Mesh.AllocateWritableMeshData(1); var data = meshDataArray[0]; data.subMeshCount = 1; var totalVertexCount = 0; var totalIndexCount = 0; var splineMeshOffsets = new (int indexStart, int vertexStart)[splines.Count]; int GetSegmentCount(T spline) { var span = Mathf.Abs(settings.Range.y - settings.Range.x); return Mathf.Max((int)Mathf.Ceil(spline.GetLength() * span * segmentsPerUnit), 1); } for (int i = 0; i < splines.Count; ++i) { if(splines[i].Count < 2) continue; settings.SegmentCount = GetSegmentCount(splines[i]); GetVertexAndIndexCount(splines[i], settings, out int vertexCount, out int indexCount); splineMeshOffsets[i] = (totalIndexCount, totalVertexCount); totalVertexCount += vertexCount; totalIndexCount += indexCount; } var indexFormat = totalVertexCount >= ushort.MaxValue ? IndexFormat.UInt32 : IndexFormat.UInt16; data.SetIndexBufferParams(totalIndexCount, indexFormat); data.SetVertexBufferParams(totalVertexCount, k_PipeVertexAttribs); var vertices = data.GetVertexData(); if (indexFormat == IndexFormat.UInt16) { var indices = data.GetIndexData(); for (int i = 0; i < splines.Count; ++i) { if (splines[i].Count < 2) continue; settings.SegmentCount = GetSegmentCount(splines[i]); Extrude(splines[i], vertices, indices, settings, splineMeshOffsets[i].vertexStart, splineMeshOffsets[i].indexStart); } } else { var indices = data.GetIndexData(); for (int i = 0; i < splines.Count; ++i) { if (splines[i].Count < 2) continue; settings.SegmentCount = GetSegmentCount(splines[i]); Extrude(splines[i], vertices, indices, settings, splineMeshOffsets[i].vertexStart, splineMeshOffsets[i].indexStart); } } data.SetSubMesh(0, new SubMeshDescriptor(0, totalIndexCount)); Mesh.ApplyAndDisposeWritableMeshData(meshDataArray, mesh); mesh.RecalculateBounds(); } /// /// Extrude a mesh along a spline in a tube-like shape. /// /// The spline to extrude. /// A pre-allocated buffer of vertex data. /// A pre-allocated index buffer. Must be of type UInt16 or UInt32. /// The radius of the extruded mesh. /// How many sides make up the radius of the mesh. /// How many sections compose the length of the mesh. /// Whether the start and end of the mesh is filled. This setting is ignored when spline /// is closed. /// /// The section of the Spline to extrude. This value expects a normalized interpolation start and end. /// I.e., [0,1] is the entire Spline, whereas [.5, 1] is the last half of the Spline. /// /// A type implementing ISpline. /// A type implementing ISplineVertexData. /// The mesh index format. Must be UInt16 or UInt32. /// An out of range exception is thrown if the vertex or index /// buffer lengths do not match the expected size. Use to calculate the /// expected buffer sizes. /// /// /// An argument exception is thrown if {TIndexType} is not UInt16 or UInt32. /// public static void Extrude( TSplineType spline, NativeArray vertices, NativeArray indices, float radius, int sides, int segments, bool capped, float2 range) where TSplineType : ISpline where TVertexType : struct, ISplineVertexData where TIndexType : struct { s_DefaultShape.SideCount = math.clamp(sides, k_SidesMin, k_SidesMax); Extrude(spline, vertices, indices, new ExtrudeSettings(segments, capped, range, radius, s_DefaultShape)); } static void Extrude( TSplineType spline, NativeArray vertices, NativeArray indices, ExtrudeSettings settings, int vertexArrayOffset = 0, int indicesArrayOffset = 0) where TSplineType : ISpline where TVertexType : struct, ISplineVertexData where TIndexType : struct where TShapeType : IExtrudeShape { var sides = settings.sides; var segments = settings.SegmentCount; var range = settings.Range; var capped = settings.DoCapEnds(spline); if (!GetVertexAndIndexCount(spline, settings, out var vertexCount, out var indexCount)) return; if (settings.Shape == null) throw new ArgumentNullException(nameof(settings.Shape), "Shape template is null."); if (sides < 2) throw new ArgumentOutOfRangeException(nameof(sides), "Sides must be greater than 2"); if (segments < 2) throw new ArgumentOutOfRangeException(nameof(segments), "Segments must be greater than 2"); if (vertices.Length < vertexCount) throw new ArgumentOutOfRangeException($"Vertex array is incorrect size. Expected {vertexCount} or more, but received {vertices.Length}."); if (indices.Length < indexCount) throw new ArgumentOutOfRangeException($"Index array is incorrect size. Expected {indexCount} or more, but received {indices.Length}."); if (typeof(TIndexType) == typeof(UInt16)) { var ushortIndices = indices.Reinterpret(); WindTris(ushortIndices, spline, settings, vertexArrayOffset, indicesArrayOffset); } else if (typeof(TIndexType) == typeof(UInt32)) { var ulongIndices = indices.Reinterpret(); WindTris(ulongIndices, spline, settings, vertexArrayOffset, indicesArrayOffset); } else { throw new ArgumentException("Indices must be UInt16 or UInt32", nameof(indices)); } var shape = settings.Shape; shape.Setup(spline, segments); s_IsConvexComputed = false; for (int i = 0; i < segments; ++i) ExtrudeRing(spline, settings, i, vertices, vertexArrayOffset + i * sides); if (capped) { var capVertexStart = vertexArrayOffset + segments * sides; var endCapVertexStart = vertexArrayOffset + (segments + 1) * sides; var rng = spline.Closed ? math.frac(range) : math.clamp(range, 0f, 1f); ExtrudeRing(spline, settings, 0, vertices, capVertexStart, true); ExtrudeRing(spline, settings, segments-1, vertices, endCapVertexStart, true); var beginAccel = math.normalize(spline.EvaluateTangent(rng.x)); var accelLen = math.lengthsq(beginAccel); if (accelLen == 0f || float.IsNaN(accelLen)) beginAccel = math.normalize(spline.EvaluateTangent(rng.x + 0.0001f)); var endAccel = math.normalize(spline.EvaluateTangent(rng.y)); accelLen = math.lengthsq(endAccel); if (accelLen == 0f || float.IsNaN(accelLen)) endAccel = math.normalize(spline.EvaluateTangent(rng.y - 0.0001f)); for (int i = 0; i < sides; ++i) { var v0 = vertices[capVertexStart + i]; var v1 = vertices[endCapVertexStart + i]; v0.normal = -beginAccel; v1.normal = endAccel; vertices[capVertexStart + i] = v0; vertices[endCapVertexStart + i] = v1; } } } // Two overloads for winding triangles because there is no generic constraint for UInt{16, 32} static void WindTris(NativeArray indices, T spline, ExtrudeSettings settings, int vertexArrayOffset = 0, int indexArrayOffset = 0) where T : ISpline where K : IExtrudeShape { var closed = settings.DoCloseSpline(spline); var segments = settings.SegmentCount; var sides = settings.sides; var wrap = settings.wrapped; var capped = settings.DoCapEnds(spline); var sideFaceCount = wrap ? sides : sides - 1; for (int i = 0; i < (closed ? segments : segments - 1); ++i) { for (int n = 0; n < (wrap ? sides : sides - 1); ++n) { var index0 = vertexArrayOffset + i * sides + n; var index1 = vertexArrayOffset + i * sides + ((n + 1) % sides); var index2 = vertexArrayOffset + ((i+1) % segments) * sides + n; var index3 = vertexArrayOffset + ((i+1) % segments) * sides + ((n + 1) % sides); indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 0] = (UInt16) index0; indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 1] = (UInt16) index1; indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 2] = (UInt16) index2; indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 3] = (UInt16) index1; indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 4] = (UInt16) index3; indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 5] = (UInt16) index2; } } if (capped) { var capVertexStart = vertexArrayOffset + segments * sides; var capIndexStart = indexArrayOffset + sideFaceCount * 6 * (segments-1); var endCapVertexStart = vertexArrayOffset + (segments + 1) * sides; var endCapIndexStart = indexArrayOffset + (segments-1) * 6 * sideFaceCount + (sides-2) * 3; for(ushort i = 0; i < sides - 2; ++i) { indices[capIndexStart + i * 3 + 0] = (UInt16)(capVertexStart); indices[capIndexStart + i * 3 + 1] = (UInt16)(capVertexStart + i + 2); indices[capIndexStart + i * 3 + 2] = (UInt16)(capVertexStart + i + 1); indices[endCapIndexStart + i * 3 + 0] = (UInt16) (endCapVertexStart); indices[endCapIndexStart + i * 3 + 1] = (UInt16) (endCapVertexStart + i + 1); indices[endCapIndexStart + i * 3 + 2] = (UInt16) (endCapVertexStart + i + 2); } } } // Two overloads for winding triangles because there is no generic constraint for UInt{16, 32} static void WindTris(NativeArray indices, T spline, ExtrudeSettings settings, int vertexArrayOffset = 0, int indexArrayOffset = 0) where T : ISpline where K : IExtrudeShape { var closed = settings.DoCloseSpline(spline); var segments = settings.SegmentCount; var sides = settings.sides; var wrap = settings.wrapped; var capped = settings.DoCapEnds(spline); var sideFaceCount = wrap ? sides : sides - 1; for (int i = 0; i < (closed ? segments : segments - 1); ++i) { for (int n = 0; n < (wrap ? sides : sides - 1); ++n) { var index0 = vertexArrayOffset + i * sides + n; var index1 = vertexArrayOffset + i * sides + ((n + 1) % sides); var index2 = vertexArrayOffset + ((i+1) % segments) * sides + n; var index3 = vertexArrayOffset + ((i+1) % segments) * sides + ((n + 1) % sides); indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 0] = (UInt16) index0; indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 1] = (UInt16) index1; indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 2] = (UInt16) index2; indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 3] = (UInt16) index1; indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 4] = (UInt16) index3; indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 5] = (UInt16) index2; } } if (capped) { var capVertexStart = vertexArrayOffset + segments * sides; var capIndexStart = indexArrayOffset + sideFaceCount * 6 * (segments-1); var endCapVertexStart = vertexArrayOffset + (segments + 1) * sides; var endCapIndexStart = indexArrayOffset + (segments-1) * 6 * sideFaceCount + (sides-2) * 3; for(ushort i = 0; i < sides - 2; ++i) { indices[capIndexStart + i * 3 + 0] = (UInt16)(capVertexStart); indices[capIndexStart + i * 3 + 1] = (UInt16)(capVertexStart + i + 2); indices[capIndexStart + i * 3 + 2] = (UInt16)(capVertexStart + i + 1); indices[endCapIndexStart + i * 3 + 0] = (UInt16) (endCapVertexStart); indices[endCapIndexStart + i * 3 + 1] = (UInt16) (endCapVertexStart + i + 1); indices[endCapIndexStart + i * 3 + 2] = (UInt16) (endCapVertexStart + i + 2); } } } } }