WuhuIslandTesting/Library/PackageCache/com.unity.splines@1.0.1/Runtime/SplineMesh.cs
2025-01-07 02:06:59 +01:00

413 lines
20 KiB
C#

using System;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine.Rendering;
namespace UnityEngine.Splines
{
/// <summary>
/// Utility methods for creating and working with meshes.
/// </summary>
public static class SplineMesh
{
const float k_RadiusMin = .00001f, k_RadiusMax = 10000f;
const int k_SidesMin = 3, k_SidesMax = 2084;
const int k_SegmentsMin = 2, k_SegmentsMax = 4096;
static readonly VertexAttributeDescriptor[] k_PipeVertexAttribs = new VertexAttributeDescriptor[]
{
new (VertexAttribute.Position),
new (VertexAttribute.Normal),
new (VertexAttribute.TexCoord0, dimension: 2)
};
/// <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<T, K>(T spline, float t, NativeArray<K> data, int start, int count, float radius)
where T : ISpline
where K : struct, ISplineVertexData
{
var evaluationT = spline.Closed ? math.frac(t) : math.clamp(t, 0f, 1f);
spline.Evaluate(evaluationT, out var sp, out var st, out _);
var length = math.lengthsq(st);
if (length == 0f || float.IsNaN(length))
st = spline.EvaluateTangent(math.clamp(evaluationT + (0.0001f * (t < 1f ? 1f : -1f)), 0f, 1f));
st = math.normalize(st);
var rot = Quaternion.LookRotation(st);
var rad = math.radians(360f / count);
for (int n = 0; n < count; ++n)
{
var vertex = new K();
var p = new Vector3(math.cos(n * rad), math.sin(n * rad), 0f) * radius;
vertex.position = (Vector3)sp + (rot * p);
vertex.normal = (vertex.position - (Vector3)sp).normalized;
// instead of inserting a seam, wrap UVs using a triangle wave so that texture wraps back onto itself
float ut = n / ((float)count + count%2);
float u = math.abs(ut - math.floor(ut + .5f)) * 2f;
vertex.texture = new Vector2(u, t * spline.GetLength());
data[start + n] = vertex;
}
}
// The logic around when caps and closing is a little complicated and easy to confuse. This wraps settings in a
// consistent way so that methods aren't working with mixed data.
struct Settings
{
public int sides { get; private set; }
public int segments { get; private set; }
public bool capped { get; private set; }
public bool closed { get; private set; }
public float2 range { get; private set; }
public float radius { get; private set; }
public Settings(int sides, int segments, bool capped, bool closed, float2 range, float radius)
{
this.sides = math.clamp(sides, k_SidesMin, k_SidesMax);
this.segments = math.clamp(segments, k_SegmentsMin, k_SegmentsMax);
this.range = new float2(math.min(range.x, range.y), math.max(range.x, range.y));
this.closed = math.abs(1f - (this.range.y - this.range.x)) < float.Epsilon && closed;
this.capped = capped && !this.closed;
this.radius = math.clamp(radius, k_RadiusMin, k_RadiusMax);
}
}
/// <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)
{
var settings = new Settings(sides, segments, capped, closed, range, 1f);
GetVertexAndIndexCount(settings, out vertexCount, out indexCount);
}
static void GetVertexAndIndexCount(Settings settings, out int vertexCount, out int indexCount)
{
vertexCount = settings.sides * (settings.segments + (settings.capped ? 2 : 0));
indexCount = settings.sides * 6 * (settings.segments - (settings.closed ? 0 : 1)) + (settings.capped ? (settings.sides - 2) * 3 * 2 : 0);
}
/// <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 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
{
var settings = new Settings(sides, segments, capped, spline.Closed, range, radius);
GetVertexAndIndexCount(settings, out var vertexCount, out var indexCount);
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, radius, sides, segments, capped, range);
}
else
{
var indices = data.GetIndexData<UInt32>();
Extrude(spline, vertices, indices, radius, sides, segments, capped, range);
}
mesh.Clear();
data.subMeshCount = 1;
data.SetSubMesh(0, new SubMeshDescriptor(0, indexCount));
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
{
Extrude(spline, vertices, indices, new Settings(sides, segments, capped, spline.Closed, range, radius));
}
static void Extrude<TSplineType, TVertexType, TIndexType>(
TSplineType spline,
NativeArray<TVertexType> vertices,
NativeArray<TIndexType> indices,
Settings settings)
where TSplineType : ISpline
where TVertexType : struct, ISplineVertexData
where TIndexType : struct
{
var radius = settings.radius;
var sides = settings.sides;
var segments = settings.segments;
var range = settings.range;
var capped = settings.capped;
var closed = settings.closed;
GetVertexAndIndexCount(settings, out var vertexCount, out var indexCount);
if (sides < 3)
throw new ArgumentOutOfRangeException(nameof(sides), "Sides must be greater than 3");
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}, but received {vertices.Length}.");
if (indices.Length != indexCount)
throw new ArgumentOutOfRangeException($"Index array is incorrect size. Expected {indexCount}, but received {indices.Length}.");
if (typeof(TIndexType) == typeof(UInt16))
{
var ushortIndices = indices.Reinterpret<UInt16>();
WindTris(ushortIndices, settings);
}
else if (typeof(TIndexType) == typeof(UInt32))
{
var ulongIndices = indices.Reinterpret<UInt32>();
WindTris(ulongIndices, settings);
}
else
{
throw new ArgumentException("Indices must be UInt16 or UInt32", nameof(indices));
}
for (int i = 0; i < segments; ++i)
ExtrudeRing(spline, math.lerp(range.x, range.y, i / (segments - 1f)), vertices, i * sides, sides, radius);
if (capped)
{
var capVertexStart = segments * sides;
var endCapVertexStart = (segments + 1) * sides;
var rng = spline.Closed ? math.frac(range) : math.clamp(range, 0f, 1f);
ExtrudeRing(spline, rng.x, vertices, capVertexStart, sides, radius);
ExtrudeRing(spline, rng.y, vertices, endCapVertexStart, sides, radius);
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));
var rad = math.radians(360f / sides);
var off = new float2(.5f, .5f);
for (int i = 0; i < sides; ++i)
{
var v0 = vertices[capVertexStart + i];
var v1 = vertices[endCapVertexStart + i];
v0.normal = -beginAccel;
v0.texture = off + new float2(math.cos(i * rad), math.sin(i * rad)) * .5f;
v1.normal = endAccel;
v1.texture = off + new float2(-math.cos(i * rad), math.sin(i * rad)) * .5f;
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<UInt16> indices, Settings settings)
{
var closed = settings.closed;
var segments = settings.segments;
var sides = settings.sides;
var capped = settings.capped;
for (int i = 0; i < (closed ? segments : segments - 1); ++i)
{
for (int n = 0; n < sides; ++n)
{
var index0 = i * sides + n;
var index1 = i * sides + ((n + 1) % sides);
var index2 = ((i+1) % segments) * sides + n;
var index3 = ((i+1) % segments) * sides + ((n + 1) % sides);
indices[i * sides * 6 + n * 6 + 0] = (UInt16) index0;
indices[i * sides * 6 + n * 6 + 1] = (UInt16) index1;
indices[i * sides * 6 + n * 6 + 2] = (UInt16) index2;
indices[i * sides * 6 + n * 6 + 3] = (UInt16) index1;
indices[i * sides * 6 + n * 6 + 4] = (UInt16) index3;
indices[i * sides * 6 + n * 6 + 5] = (UInt16) index2;
}
}
if (capped)
{
var capVertexStart = segments * sides;
var capIndexStart = sides * 6 * (segments-1);
var endCapVertexStart = (segments + 1) * sides;
var endCapIndexStart = (segments-1) * 6 * sides + (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<UInt32> indices, Settings settings)
{
var closed = settings.closed;
var segments = settings.segments;
var sides = settings.sides;
var capped = settings.capped;
for (int i = 0; i < (closed ? segments : segments - 1); ++i)
{
for (int n = 0; n < sides; ++n)
{
var index0 = i * sides + n;
var index1 = i * sides + ((n + 1) % sides);
var index2 = ((i+1) % segments) * sides + n;
var index3 = ((i+1) % segments) * sides + ((n + 1) % sides);
indices[i * sides * 6 + n * 6 + 0] = (UInt32) index0;
indices[i * sides * 6 + n * 6 + 1] = (UInt32) index1;
indices[i * sides * 6 + n * 6 + 2] = (UInt32) index2;
indices[i * sides * 6 + n * 6 + 3] = (UInt32) index1;
indices[i * sides * 6 + n * 6 + 4] = (UInt32) index3;
indices[i * sides * 6 + n * 6 + 5] = (UInt32) index2;
}
}
if (capped)
{
var capVertexStart = segments * sides;
var capIndexStart = sides * 6 * (segments-1);
var endCapVertexStart = (segments + 1) * sides;
var endCapIndexStart = (segments-1) * 6 * sides + (sides-2) * 3;
for(ushort i = 0; i < sides - 2; ++i)
{
indices[capIndexStart + i * 3 + 0] = (UInt32)(capVertexStart);
indices[capIndexStart + i * 3 + 1] = (UInt32)(capVertexStart + i + 2);
indices[capIndexStart + i * 3 + 2] = (UInt32)(capVertexStart + i + 1);
indices[endCapIndexStart + i * 3 + 0] = (UInt32) (endCapVertexStart);
indices[endCapIndexStart + i * 3 + 1] = (UInt32) (endCapVertexStart + i + 1);
indices[endCapIndexStart + i * 3 + 2] = (UInt32) (endCapVertexStart + i + 2);
}
}
}
}
}