using UnityEngine;
using System.Collections.Generic;
using System.Linq;
namespace UnityEngine.ProBuilder
{
static class Spline
{
///
/// Create a new pb_Object by extruding along a bezier spline.
///
/// The points making up the bezier spline.
/// The radius of the extruded mesh tube.
/// How many columns per segment to create when extruding the mesh.
/// How many rows the extruded mesh will be composed of.
/// Should the mesh join at the ends or remain unconnected.
/// Are the mesh edges smoothed or hard.
/// The resulting pb_Object.
internal static ProBuilderMesh Extrude(IList points,
float radius = .5f,
int columns = 32,
int rows = 16,
bool closeLoop = false,
bool smooth = true)
{
ProBuilderMesh pb = null;
Extrude(points, radius, columns, rows, closeLoop, smooth, ref pb);
return pb;
}
// Update a pb_Object with new geometry from a bezier spline.
internal static void Extrude(IList bezierPoints,
float radius,
int columns,
int rows,
bool closeLoop,
bool smooth,
ref ProBuilderMesh target)
{
List rotations = new List();
List positions = GetControlPoints(bezierPoints, columns, closeLoop, rotations);
Extrude(positions, radius, rows, closeLoop, smooth, ref target, rotations);
}
// Extrapolate a bezier curve to it's control points and segments between.
internal static List GetControlPoints(IList bezierPoints, int subdivisionsPerSegment, bool closeLoop, List rotations)
{
int cols = subdivisionsPerSegment;
int c = bezierPoints.Count;
List positions = new List(cols * c);
if (rotations != null)
{
rotations.Clear();
rotations.Capacity = cols * c;
}
int keyframes = (closeLoop ? c : c - 1);
for (int i = 0; i < keyframes; i++)
{
int segments_per_keyframe = ((!closeLoop && i >= c - 2) ? cols + 1 : cols);
for (int n = 0; n < segments_per_keyframe; n++)
{
float s = cols;
positions.Add(BezierPoint.CubicPosition(bezierPoints[i], bezierPoints[(i + 1) % c], n / s));
if (rotations != null)
rotations.Add(Quaternion.Slerp(bezierPoints[i].rotation, bezierPoints[(i + 1) % c].rotation, n / (float)(segments_per_keyframe - 1)));
}
}
return positions;
}
// Set mesh geometry by extruding along a set of points.
internal static void Extrude(IList points,
float radius,
int radiusRows,
bool closeLoop,
bool smooth,
ref ProBuilderMesh target,
IList pointRotations = null)
{
if (points == null || points.Count < 2)
return;
int cnt = points.Count;
int rows = System.Math.Max(3, radiusRows);
int rowsPlus1 = rows + 1;
int rowsPlus1Times2 = rows * 2;
int vertexCount = ((closeLoop ? cnt : cnt - 1) * 2) * rowsPlus1Times2;
bool vertexCountsMatch = false; // vertexCount == (target == null ? 0 : target.vertexCount);
bool hasPointRotations = pointRotations != null && pointRotations.Count == points.Count;
Vector3[] positions = new Vector3[vertexCount];
Face[] faces = vertexCountsMatch ? null : new Face[(closeLoop ? cnt : cnt - 1) * rows];
int triangleIndex = 0, faceIndex = 0, vertexIndex = 0;
int segmentCount = (closeLoop ? cnt : cnt - 1);
for (int i = 0; i < segmentCount; i++)
{
float secant_a, secant_b;
Quaternion rotation_a = GetRingRotation(points, i, closeLoop, out secant_a);
Quaternion rotation_b = GetRingRotation(points, (i + 1) % cnt, closeLoop, out secant_b);
if (hasPointRotations)
{
rotation_a = rotation_a * pointRotations[i];
rotation_b = rotation_b * pointRotations[(i + 1) % cnt];
}
Vector3[] ringA = VertexRing(rotation_a, points[i], radius, rowsPlus1);
Vector3[] ringB = VertexRing(rotation_b, points[(i + 1) % cnt], radius, rowsPlus1);
System.Array.Copy(ringA, 0, positions, vertexIndex, rowsPlus1Times2);
vertexIndex += rowsPlus1Times2;
System.Array.Copy(ringB, 0, positions, vertexIndex, rowsPlus1Times2);
vertexIndex += rowsPlus1Times2;
if (!vertexCountsMatch)
{
for (int n = 0; n < rowsPlus1Times2; n += 2)
{
faces[faceIndex] = new Face(new int[6]
{
triangleIndex, triangleIndex + 1, triangleIndex + rowsPlus1Times2,
triangleIndex + rowsPlus1Times2, triangleIndex + 1, triangleIndex + rowsPlus1Times2 + 1
});
if (smooth)
faces[faceIndex].smoothingGroup = 2;
faceIndex++;
triangleIndex += 2;
}
triangleIndex += rowsPlus1Times2;
}
}
if (target != null)
{
if (faces != null)
{
target.RebuildWithPositionsAndFaces(positions, faces);
}
else
{
target.positions = positions;
target.ToMesh();
target.Refresh(RefreshMask.UV | RefreshMask.Colors | RefreshMask.Normals | RefreshMask.Tangents);
}
}
else
{
target = ProBuilderMesh.Create(positions, faces);
}
}
static Quaternion GetRingRotation(IList points, int i, bool closeLoop, out float secant)
{
int cnt = points.Count;
Vector3 dir;
if (closeLoop || (i > 0 && i < cnt - 1))
{
int a = i < 1 ? cnt - 1 : i - 1;
int b = i;
int c = (i + 1) % cnt;
Vector3 coming = (points[b] - points[a]).normalized;
Vector3 leaving = (points[c] - points[b]).normalized;
dir = (coming + leaving) * .5f;
secant = Math.Secant(Vector3.Angle(coming, dir) * Mathf.Deg2Rad);
}
else
{
if (i < 1)
dir = points[i + 1] - points[i];
else
dir = points[i] - points[i - 1];
secant = 1f;
}
dir.Normalize();
if (Math.Approx3(dir, Vector3.up) || Math.Approx3(dir, Vector3.zero))
return Quaternion.identity;
return Quaternion.LookRotation(dir);
}
static Vector3[] VertexRing(Quaternion orientation, Vector3 offset, float radius, int segments)
{
Vector3[] v = new Vector3[segments * 2];
for (int i = 0; i < segments; i++)
{
float rad0 = (i / (float)(segments - 1)) * 360f * Mathf.Deg2Rad;
int n = (i + 1) % segments;
float rad1 = (n / (float)(segments - 1)) * 360f * Mathf.Deg2Rad;
v[i * 2] = offset + (orientation * new Vector3(Mathf.Cos(rad0) * radius, Mathf.Sin(rad0) * radius, 0f));
v[i * 2 + 1] = offset + (orientation * new Vector3(Mathf.Cos(rad1) * radius, Mathf.Sin(rad1) * radius, 0f));
}
return v;
}
}
}