857 lines
40 KiB
C#
857 lines
40 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Unity.Collections;
|
|
using Unity.Mathematics;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.Splines.ExtrusionShapes;
|
|
|
|
namespace UnityEngine.Splines
|
|
{
|
|
/// <summary>
|
|
/// Contains settings pertaining to the creation of mesh geometry for an
|
|
/// extruded shape. Use with <see cref="SplineMesh"/>.
|
|
/// </summary>
|
|
/// <typeparam name="T">A type implementing <see cref="IExtrudeShape"/>.</typeparam>
|
|
public struct ExtrudeSettings<T> 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;
|
|
|
|
/// <summary>
|
|
/// How many sections compose the length of the mesh.
|
|
/// </summary>
|
|
public int SegmentCount
|
|
{
|
|
get => m_SegmentCount;
|
|
set => m_SegmentCount = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public bool CapEnds
|
|
{
|
|
get => m_CapEnds;
|
|
set => m_CapEnds = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set true to reverse the winding order of vertices so that the face normals are inverted. This is useful
|
|
/// primarily for <see cref="SplineShape"/> templates where the input path may not produce a counter-clockwise
|
|
/// vertex ring. Counter-clockwise winding equates to triangles facing outwards.
|
|
/// </summary>
|
|
public bool FlipNormals
|
|
{
|
|
get => m_FlipNormals;
|
|
set => m_FlipNormals = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The radius of the extruded mesh. Radius is half of the width of the entire shape.
|
|
/// The return value of <see cref="IExtrudeShape.GetPosition"/> is multiplied by this
|
|
/// value to determine the size of the resulting shape.
|
|
/// </summary>
|
|
public float Radius
|
|
{
|
|
get => m_Radius;
|
|
set => m_Radius = math.clamp(value, k_RadiusMin, k_RadiusMax);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The <see cref="IExtrudeShape"/> object defines the outline path of each segment.
|
|
/// </summary>
|
|
public T Shape
|
|
{
|
|
get => m_Shape;
|
|
set => m_Shape = value;
|
|
}
|
|
|
|
internal bool DoCapEnds<K>(K spline) where K : ISpline => m_CapEnds && !spline.Closed;
|
|
|
|
internal bool DoCloseSpline<K>(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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new settings object with an <see cref="IExtrudeShape"/>
|
|
/// instance. A default set of parameters will be used.
|
|
/// </summary>
|
|
/// <param name="shape">The <see cref="IExtrudeShape"/> template to
|
|
/// be used as the shape template when extruding.</param>
|
|
public ExtrudeSettings(T shape) : this(16, false, new float2(0, 1), .5f, shape)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new settings object. This is used by functions in
|
|
/// <see cref="SplineMesh"/> to extrude a shape template along a spline.
|
|
/// </summary>
|
|
/// <param name="segments">The number of segments to divide the extruded spline into when creating vertex rings.</param>
|
|
/// <param name="capped">Defines whether the ends of the extruded spline mesh should be closed.</param>
|
|
/// <param name="range">The start and end points as normalized interpolation values.</param>
|
|
/// <param name="radius">Defines the size of the extruded mesh.</param>
|
|
/// <param name="shape">The <see cref="IExtrudeShape"/> template to
|
|
/// be used as the shape template when extruding.</param>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Static functions for extruding meshes along <see cref="Spline"/> paths.
|
|
/// Use this class to build extruded meshes from code, or <see cref="SplineExtrude"/>
|
|
/// for a pre-built Component that can be attached to any GameObject with
|
|
/// a <see cref="SplineContainer"/>.
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// 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 <see cref="SplineMesh"/>."/>.
|
|
/// </summary>
|
|
public interface ISplineVertexData
|
|
{
|
|
/// <summary>
|
|
/// Vertex position.
|
|
/// </summary>
|
|
public Vector3 position { get; set; }
|
|
|
|
/// <summary>
|
|
/// Vertex normal.
|
|
/// </summary>
|
|
public Vector3 normal { get; set; }
|
|
|
|
/// <summary>
|
|
/// Vertex texture, corresponds to UV0.
|
|
/// </summary>
|
|
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, TShape, TVertex>(
|
|
TSpline spline,
|
|
ExtrudeSettings<TShape> settings,
|
|
int segment,
|
|
NativeArray<TVertex> 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<TVertex>(
|
|
NativeArray<TVertex> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate the vertex and index count required for an extruded mesh.
|
|
/// Use this method to allocate attribute and index buffers for use with Extrude.
|
|
/// </summary>
|
|
/// <param name="closeRing">Whether the extruded vertex ring is open (has a start and end point) or closed (forms an unbroken loop).</param>
|
|
/// <param name="vertexCount">The number of vertices required for an extruded mesh using the provided settings.</param>
|
|
/// <param name="indexCount">The number of indices required for an extruded mesh using the provided settings.</param>
|
|
/// <param name="sides">How many sides make up the radius of the mesh.</param>
|
|
/// <param name="segments">How many sections compose the length of the mesh.</param>
|
|
/// <param name="capped">Whether the start and end of the mesh is filled. This setting is ignored when spline is closed.</param>
|
|
/// <param name="closed">Whether the extruded mesh is closed or open. This can be separate from the Spline.Closed value.</param>
|
|
/// <returns>Returns true if the computed vertex count exceeds 3 and the computed index count exceeds 5.</returns>
|
|
// 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate the vertex and index count required for an extruded mesh.
|
|
/// Use this method to allocate attribute and index buffers for use with Extrude.
|
|
/// </summary>
|
|
/// <param name="vertexCount">The number of vertices required for an extruded mesh using the provided settings.</param>
|
|
/// <param name="indexCount">The number of indices required for an extruded mesh using the provided settings.</param>
|
|
/// <param name="sides">How many sides make up the radius of the mesh.</param>
|
|
/// <param name="segments">How many sections compose the length of the mesh.</param>
|
|
/// <param name="range">
|
|
/// 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.
|
|
/// </param>
|
|
/// <param name="capped">Whether the start and end of the mesh is filled. This setting is ignored when spline is closed.</param>
|
|
/// <param name="closed">Whether the extruded mesh is closed or open. This can be separate from the Spline.Closed value.</param>
|
|
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, K>(T spline, ExtrudeSettings<K> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extrude a mesh along a spline in a tube-like shape.
|
|
/// </summary>
|
|
/// <param name="spline">The spline to extrude.</param>
|
|
/// <param name="mesh">A mesh that will be cleared and filled with vertex data for the shape.</param>
|
|
/// <param name="radius">The radius of the extruded mesh.</param>
|
|
/// <param name="sides">How many sides make up the radius of the mesh.</param>
|
|
/// <param name="segments">How many sections compose the length of the mesh.</param>
|
|
/// <param name="capped">Whether the start and end of the mesh is filled. This setting is ignored when spline is closed.</param>
|
|
/// <typeparam name="T">A type implementing ISpline.</typeparam>
|
|
public static void Extrude<T>(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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extrude a mesh along a spline with a customised shape.
|
|
/// </summary>
|
|
/// <param name="spline">The spline to extrude.</param>
|
|
/// <param name="mesh">A mesh that will be cleared and filled with vertex data for the shape.</param>
|
|
/// <param name="radius">The radius of the extruded mesh.</param>
|
|
/// <param name="segments">How many sections compose the length of the mesh.</param>
|
|
/// <param name="capped">Whether the start and end of the mesh is filled. This setting is ignored when spline is closed.</param>
|
|
/// <param name="shape">The <see cref="IExtrudeShape"/> object defines the outline path of each segment.</param>
|
|
/// <typeparam name="T">A type implementing ISpline.</typeparam>
|
|
/// <typeparam name="K">A type implementing <see cref="IExtrudeShape"/>.</typeparam>
|
|
public static void Extrude<T, K>(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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extrude a mesh along a spline in a tube-like shape.
|
|
/// </summary>
|
|
/// <param name="spline">The spline to extrude.</param>
|
|
/// <param name="mesh">A mesh that will be cleared and filled with vertex data for the shape.</param>
|
|
/// <param name="radius">The radius of the extruded mesh.</param>
|
|
/// <param name="sides">How many sides make up the radius of the mesh.</param>
|
|
/// <param name="segments">How many sections compose the length of the mesh.</param>
|
|
/// <param name="capped">Whether the start and end of the mesh is filled. This setting is ignored when spline is closed.</param>
|
|
/// <param name="range">
|
|
/// 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.
|
|
/// </param>
|
|
/// <typeparam name="T">A type implementing ISpline.</typeparam>
|
|
public static void Extrude<T>(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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extrude a mesh along a spline following a shape template.
|
|
/// </summary>
|
|
/// <param name="spline">The spline to extrude.</param>
|
|
/// <param name="mesh">A mesh that will be cleared and filled with vertex data for the shape.</param>
|
|
/// <param name="radius">The radius of the extruded mesh.</param>
|
|
/// <param name="segments">How many sections compose the length of the mesh.</param>
|
|
/// <param name="capped">Whether the start and end of the mesh is filled. This setting is ignored when spline is closed.</param>
|
|
/// <param name="range">
|
|
/// 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.
|
|
/// </param>
|
|
/// <param name="shape">The <see cref="IExtrudeShape"/> object defines the outline path of each segment.</param>
|
|
/// <typeparam name="T">A type implementing ISpline.</typeparam>
|
|
/// <typeparam name="K">A type implementing <see cref="IExtrudeShape"/>.</typeparam>
|
|
public static void Extrude<T, K>(T spline, Mesh mesh, float radius, int segments, bool capped, float2 range,
|
|
K shape)
|
|
where T : ISpline
|
|
where K : IExtrudeShape
|
|
{
|
|
var settings = new ExtrudeSettings<K>()
|
|
{
|
|
Radius = radius,
|
|
CapEnds = capped,
|
|
Range = range,
|
|
SegmentCount = segments,
|
|
Shape = shape
|
|
};
|
|
|
|
Extrude(spline, mesh, settings);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extrude a mesh along a spline following a shape template.
|
|
/// </summary>
|
|
/// <param name="spline">The spline to extrude.</param>
|
|
/// <param name="mesh">A mesh that will be cleared and filled with vertex data for the shape.</param>
|
|
/// <param name="settings">The <see cref="ExtrudeSettings{T}"/> parameters used when creating mesh geometry.</param>
|
|
/// <typeparam name="T">A type implementing ISpline.</typeparam>
|
|
/// <typeparam name="K">A type implementing <see cref="IExtrudeShape"/>.</typeparam>
|
|
/// <returns>Returns true if mesh was created, or false if the settings configuration resulted in an invalid state (ex, too few vertices).</returns>
|
|
public static bool Extrude<T, K>(T spline, Mesh mesh, ExtrudeSettings<K> 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<VertexData>();
|
|
|
|
if (indexFormat == IndexFormat.UInt16)
|
|
{
|
|
var indices = data.GetIndexData<UInt16>();
|
|
Extrude(spline, vertices, indices, settings);
|
|
}
|
|
else
|
|
{
|
|
var indices = data.GetIndexData<UInt32>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extrude a mesh along a list of splines in a tube-like shape.
|
|
/// </summary>
|
|
/// <param name="splines">The splines to extrude.</param>
|
|
/// <param name="mesh">A mesh that will be cleared and filled with vertex data for the shape.</param>
|
|
/// <param name="radius">The radius of the extruded mesh.</param>
|
|
/// <param name="sides">How many sides make up the radius of the mesh.</param>
|
|
/// <param name="segmentsPerUnit">The number of edge loops that comprise the length of one unit of the mesh.</param>
|
|
/// <param name="capped">Whether the start and end of the mesh is filled. This setting is ignored when spline is closed.</param>
|
|
/// <param name="range">
|
|
/// 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.
|
|
/// </param>
|
|
/// <typeparam name="T">A type implementing ISpline.</typeparam>
|
|
public static void Extrude<T>(IReadOnlyList<T> splines, Mesh mesh, float radius, int sides, float segmentsPerUnit, bool capped, float2 range) where T : ISpline
|
|
{
|
|
s_DefaultShape.SideCount = sides;
|
|
|
|
var settings = new ExtrudeSettings<Circle>(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<T, K>(IReadOnlyList<T> splines, Mesh mesh, ExtrudeSettings<K> 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<VertexData>();
|
|
if (indexFormat == IndexFormat.UInt16)
|
|
{
|
|
var indices = data.GetIndexData<UInt16>();
|
|
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<UInt32>();
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extrude a mesh along a spline in a tube-like shape.
|
|
/// </summary>
|
|
/// <param name="spline">The spline to extrude.</param>
|
|
/// <param name="vertices">A pre-allocated buffer of vertex data.</param>
|
|
/// <param name="indices">A pre-allocated index buffer. Must be of type UInt16 or UInt32.</param>
|
|
/// <param name="radius">The radius of the extruded mesh.</param>
|
|
/// <param name="sides">How many sides make up the radius of the mesh.</param>
|
|
/// <param name="segments">How many sections compose the length of the mesh.</param>
|
|
/// <param name="capped">Whether the start and end of the mesh is filled. This setting is ignored when spline
|
|
/// is closed.</param>
|
|
/// <param name="range">
|
|
/// 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.
|
|
/// </param>
|
|
/// <typeparam name="TSplineType">A type implementing ISpline.</typeparam>
|
|
/// <typeparam name="TVertexType">A type implementing ISplineVertexData.</typeparam>
|
|
/// <typeparam name="TIndexType">The mesh index format. Must be UInt16 or UInt32.</typeparam>
|
|
/// <exception cref="ArgumentOutOfRangeException">An out of range exception is thrown if the vertex or index
|
|
/// buffer lengths do not match the expected size. Use <see cref="GetVertexAndIndexCount"/> to calculate the
|
|
/// expected buffer sizes.
|
|
/// </exception>
|
|
/// <exception cref="ArgumentException">
|
|
/// An argument exception is thrown if {TIndexType} is not UInt16 or UInt32.
|
|
/// </exception>
|
|
public static void Extrude<TSplineType, TVertexType, TIndexType>(
|
|
TSplineType spline,
|
|
NativeArray<TVertexType> vertices,
|
|
NativeArray<TIndexType> 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<Circle>(segments, capped, range, radius, s_DefaultShape));
|
|
}
|
|
|
|
static void Extrude<TSplineType, TVertexType, TIndexType, TShapeType>(
|
|
TSplineType spline,
|
|
NativeArray<TVertexType> vertices,
|
|
NativeArray<TIndexType> indices,
|
|
ExtrudeSettings<TShapeType> 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<UInt16>();
|
|
WindTris(ushortIndices, spline, settings, vertexArrayOffset, indicesArrayOffset);
|
|
}
|
|
else if (typeof(TIndexType) == typeof(UInt32))
|
|
{
|
|
var ulongIndices = indices.Reinterpret<UInt32>();
|
|
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<T, K>(NativeArray<UInt16> indices, T spline, ExtrudeSettings<K> 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<T, K>(NativeArray<UInt32> indices, T spline, ExtrudeSettings<K> 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|