原文地址:http://www.cnblogs.com/hellohuan/p/3478907.html
U3D——刀光轨迹插件的改进
之前在PC端的游戏中实现过轨迹,算法喜闻乐见,收集每帧的控制点,对其进行B样条插值,生成D3DTriStrip。
这两天刚刚接触U3D,美术给了一个轨迹的插件,要求我扩展脚本,支持锁链刀弯刀的刀光计算,暂且命名多控制点的轨迹。
算是U3D的第一个纯技术需求吧,记录一下。
新增加了一个脚本:Hello_MeleeWeaponTrail.cs,增加了多控制点编辑,调整使用了NewCatmullRom差值算法(使用线性和NewBezier效果不好)。

刀光的效果如下:
根据前东家的规范,写了一下方案的非功能性评估:
通过Unity的stat面板并未发现与之前性能明显的区别。
不过算法会动态生成TriangleMesh,控制点越多生成顶点和三角形数会增加,影响内存显存和显卡带宽填充。
空间:
内存
单个顶点position+uv+vcColor大约32字节。下表展示增加控制点增加的内存消耗。
控制点数量 |
2 |
3 |
n |
顶点数量 |
2X |
3X |
nX |
三角形数量 |
X |
2X |
(N-1)X |
显存:
顶点和索引会增加相应空间的显存。
时间:
CPU:与控制点数量的时间复杂度为O(n)。
GPU:随着顶点数量的增加,VS阶段带宽增加,绘制的三角形数量变多。
总体来讲,效率影响不是太大,场景中刀光数量不多的话,使用无大问题。
建议:
逻辑来控制刀光的Emit,即,只在挥刀的时候置成true,其他时候是false,再精致点就要用动画的时间控制刀光的显示和隐藏,比如只有当刀砍下时有刀光,抬起时没有刀光等。
代码1:#define USE_INTERPOLATION
//
// By Anomalous Underdog, 2011
//
// Based on code made by Forest Johnson (Yoggy) and xyber
// 修正致命内存错误么
//
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Hello_MeleeWeaponTrail : MonoBehaviour
{
[SerializeField]
bool _emit = true;
public bool Emit { set{_emit = value;} }
bool _use = true;
public bool Use { set{_use = value;} }
[SerializeField]
float _emitTime = 0.0f;
[SerializeField]
Material _material;
[SerializeField]
float _lifeTime = 1.0f;
[SerializeField]
Color[] _colors;
[SerializeField]
float[] _sizes;
[SerializeField]
float _minVertexDistance = 0.1f;
[SerializeField]
float _maxVertexDistance = 10.0f;
float _minVertexDistanceSqr = 0.0f;
float _maxVertexDistanceSqr = 0.0f;
[SerializeField]
float _maxAngle = 3.00f;
[SerializeField]
bool _autoDestruct = false;
#if USE_INTERPOLATION
[SerializeField]
int subdivisions = 4;
#endif
[SerializeField]
Transform[] _transforms;
List<Point> _points = new List<Point>();
#if USE_INTERPOLATION
List<Point> _smoothedPoints = new List<Point>();
#endif
GameObject _trailObject;
Mesh _trailMesh;
Vector3 _lastPosition;
[System.Serializable]
public class Point
{
public float timeCreated = 0.0f;
public List<Vector3> allVectors= new List<Vector3>();
public void RemoveVectors()
{
allVectors.Clear ();
}
}
void Start()
{
_lastPosition = transform.position;
_trailObject = new GameObject("Trail");
_trailObject.transform.parent = null;
_trailObject.transform.position = Vector3.zero;
_trailObject.transform.rotation = Quaternion.identity;
_trailObject.transform.localScale = Vector3.one;
_trailObject.AddComponent(typeof(MeshFilter));
_trailObject.AddComponent(typeof(MeshRenderer));
_trailObject.renderer.material = _material;
_trailMesh = new Mesh();
_trailMesh.name = name + "TrailMesh";
_trailObject.GetComponent<MeshFilter>().mesh = _trailMesh;
_minVertexDistanceSqr = _minVertexDistance * _minVertexDistance;
_maxVertexDistanceSqr = _maxVertexDistance * _maxVertexDistance;
}
void OnDisable()
{
Destroy(_trailObject);
}
void Update()
{
if (!_use)
{
return;
}
if (_transforms.Length < 2) {
return ;
}
if (_emit && _emitTime != 0)
{
_emitTime -= Time.deltaTime;
if (_emitTime == 0) _emitTime = -1;
if (_emitTime < 0) _emit = false;
}
if (!_emit && _points.Count == 0 && _autoDestruct)
{
Destroy(_trailObject);
Destroy(gameObject);
}
// early out if there is no camera
if (!Camera.main) return;
// if we have moved enough, create a new vertex and make sure we rebuild the mesh
float theDistanceSqr = (_lastPosition - transform.position).sqrMagnitude;
if (_emit)
{
if (theDistanceSqr > _minVertexDistanceSqr)
{
bool make = false;
if (_points.Count < 3)
{
make = true;
}
else
{
//Vector3 l1 = _points[_points.Count - 2].basePosition - _points[_points.Count - 3].basePosition;
//Vector3 l2 = _points[_points.Count - 1].basePosition - _points[_points.Count - 2].basePosition;
Vector3 l1 = _points[_points.Count - 2].allVectors[0] - _points[_points.Count - 3].allVectors[0];
Vector3 l2 = _points[_points.Count - 1].allVectors[0] - _points[_points.Count - 2].allVectors[0];
if (Vector3.Angle(l1, l2) > _maxAngle || theDistanceSqr > _maxVertexDistanceSqr) make = true;
}
if (make)
{
Point p = new Point();
//p.allVectors.Clear();
for(int i = 0; i < _transforms.Length; ++i)
{
p.allVectors.Add(_transforms[i].position);
}
p.timeCreated = Time.time;
_points.Add(p);
_lastPosition = transform.position;
#if USE_INTERPOLATION
if (_points.Count == 1)
{
_smoothedPoints.Add(p);
}
else if (_points.Count > 1)
{
// add 1+subdivisions for every possible pair in the _points
for (int n = 0; n < 1+subdivisions; ++n)
_smoothedPoints.Add(p);
}
// we use 4 control points for the smoothing
if (_points.Count >= 4)
{
List<List<Vector3> > vecsmooths = new List< List<Vector3> >();
int transformsSize = _points[_points.Count - 4].allVectors.Count;
int smoothTipListCount = 0 ;
for(int i = 0 ; i < transformsSize; ++i)
{
//Debug.LogError( "transformsSize" + transformsSize );
Vector3[] tipPoints = new Vector3[4];
tipPoints[0] = _points[_points.Count - 4].allVectors[i];
tipPoints[1] = _points[_points.Count - 3].allVectors[i];
tipPoints[2] = _points[_points.Count - 2].allVectors[i];
tipPoints[3] = _points[_points.Count - 1].allVectors[i];
/// donot use Bezier HelloHuan;
//IEnumerable<Vector3> smoothTip = Interpolate.NewBezier(Interpolate.Ease(Interpolate.EaseType.Linear), tipPoints, subdivisions);
IEnumerable<Vector3> smoothTip = Interpolate.NewCatmullRom(tipPoints, subdivisions, false);
List<Vector3> Tmp = new List<Vector3>(smoothTip);
smoothTipListCount = Tmp.Count;
//Debug.LogError( "smoothTipListCount\t\t" + smoothTipListCount );
vecsmooths.Add( Tmp );
}
float firstTime = _points[_points.Count - 4].timeCreated;
float secondTime = _points[_points.Count - 1].timeCreated;
//Debug.Log(" smoothTipList.Count: " + smoothTipList.Count);
for (int n = 0; n <smoothTipListCount; ++n)
{
int idx = _smoothedPoints.Count - (smoothTipListCount-n);
// there are moments when the _smoothedPoints are lesser
// than what is required, when elements from it are removed
if (idx > -1 && idx < _smoothedPoints.Count)
{
Point sp = new Point();
for(int i = 0; i < transformsSize; ++i )
{
sp.allVectors.Add( vecsmooths[i][n] );
}
sp.timeCreated = Mathf.Lerp(firstTime, secondTime, (float)n/ smoothTipListCount);
_smoothedPoints[idx] = sp;
}
//else
//{
// Debug.LogError(idx + "/" + _smoothedPoints.Count);
//}
}
}
#endif
}
else
{
if(_points[_points.Count - 1].allVectors.Count == _transforms.Length)
{
for(int i = 0; i < _transforms.Length; ++i)
{
_points[_points.Count - 1].allVectors[i] = (_transforms[i].position);
}
}
else{
_points[_points.Count - 1].RemoveVectors();
for(int i = 0; i < _transforms.Length; ++i)
{
_points[_points.Count - 1].allVectors.Add(_transforms[i].position);
}
}
//_points[_points.Count - 1].timeCreated = Time.time;
#if USE_INTERPOLATION
// _smoothedPoints[_smoothedPoints.Count - 1].RemoveVectors();
// for(int i = 0; i < _transforms.Length; ++i)
// {
// _smoothedPoints[_smoothedPoints.Count - 1].allVectors.Add(_transforms[i].position);
// }
if(_smoothedPoints[_smoothedPoints.Count - 1].allVectors.Count == _transforms.Length)
{
for(int i = 0; i < _transforms.Length; ++i)
{
_smoothedPoints[_smoothedPoints.Count - 1].allVectors[i] = (_transforms[i].position);
}
}
else{
_smoothedPoints[_smoothedPoints.Count - 1].RemoveVectors();
for(int i = 0; i < _transforms.Length; ++i)
{
_smoothedPoints[_smoothedPoints.Count - 1].allVectors.Add(_transforms[i].position);
}
}
#endif
}
}
else
{
if (_points.Count > 0)
{
// for(int i = 0; i < _transforms.Length; ++i)
// {
// _points[_points.Count - 1].allVectors.Add(_transforms[i].position);
// }
if(_points[_points.Count - 1].allVectors.Count == _transforms.Length)
{
for(int i = 0; i < _transforms.Length; ++i)
{
_points[_points.Count - 1].allVectors[i] = (_transforms[i].position);
}
}
else{
_points[_points.Count - 1].RemoveVectors();
for(int i = 0; i < _transforms.Length; ++i)
{
_points[_points.Count - 1].allVectors.Add(_transforms[i].position);
}
}
//_points[_points.Count - 1].timeCreated = Time.time;
}
#if USE_INTERPOLATION
if (_smoothedPoints.Count > 0)
{
// for(int i = 0; i < _transforms.Length; ++i)
// {
// _smoothedPoints[_smoothedPoints.Count - 1].allVectors.Add(_transforms[i].position);
// }
if(_smoothedPoints[_smoothedPoints.Count - 1].allVectors.Count == _transforms.Length)
{
for(int i = 0; i < _transforms.Length; ++i)
{
_smoothedPoints[_smoothedPoints.Count - 1].allVectors[i] = (_transforms[i].position);
}
}
else{
_smoothedPoints[_smoothedPoints.Count - 1].RemoveVectors();
for(int i = 0; i < _transforms.Length; ++i)
{
_smoothedPoints[_smoothedPoints.Count - 1].allVectors.Add(_transforms[i].position);
}
}
}
#endif
}
}
RemoveOldPoints(_points);
if (_points.Count == 0)
{
_trailMesh.Clear();
}
#if USE_INTERPOLATION
RemoveOldPoints(_smoothedPoints);
if (_smoothedPoints.Count == 0)
{
_trailMesh.Clear();
}
#endif
#if USE_INTERPOLATION
List<Point> pointsToUse = _smoothedPoints;
#else
List<Point> pointsToUse = _points;
#endif
if (pointsToUse.Count > 1)
{
int sectionPointSize = _transforms.Length;
Vector3[] newVertices = new Vector3[pointsToUse.Count * sectionPointSize];
Vector2[] newUV = new Vector2[pointsToUse.Count * sectionPointSize];
int[] newTriangles = new int[(pointsToUse.Count - 1) * 6*(sectionPointSize - 1)];
Color[] newColors = new Color[pointsToUse.Count * sectionPointSize];
for (int n = 0; n < pointsToUse.Count; ++n)
{
Point p = pointsToUse[n];
float time = (Time.time - p.timeCreated) / _lifeTime;
Color color = Color.Lerp(Color.white, Color.clear, time);
if (_colors != null && _colors.Length > 0)
{
float colorTime = time * (_colors.Length - 1);
float min = Mathf.Floor(colorTime);
float max = Mathf.Clamp(Mathf.Ceil(colorTime), 1, _colors.Length - 1);
float lerp = Mathf.InverseLerp(min, max, colorTime);
if (min >= _colors.Length) min = _colors.Length - 1; if (min < 0) min = 0;
if (max >= _colors.Length) max = _colors.Length - 1; if (max < 0) max = 0;
color = Color.Lerp(_colors[(int)min], _colors[(int)max], lerp);
}
float size = 0f;
if (_sizes != null && _sizes.Length > 0)
{
float sizeTime = time * (_sizes.Length - 1);
float min = Mathf.Floor(sizeTime);
float max = Mathf.Clamp(Mathf.Ceil(sizeTime), 1, _sizes.Length - 1);
float lerp = Mathf.InverseLerp(min, max, sizeTime);
if (min >= _sizes.Length) min = _sizes.Length - 1; if (min < 0) min = 0;
if (max >= _sizes.Length) max = _sizes.Length - 1; if (max < 0) max = 0;
size = Mathf.Lerp(_sizes[(int)min], _sizes[(int)max], lerp);
}
Vector3 lineDirection = p.allVectors[sectionPointSize-1] - p.allVectors[0] ;
//newVertices[n * 2] = p.basePosition - (lineDirection * (size * 0.5f));
//newVertices[(n * 2) + 1] = p.tipPosition + (lineDirection * (size * 0.5f));
float deltaRatioxx = 1.0F/(sectionPointSize-1);
float uvRatio = (float)n/pointsToUse.Count;
for(int i = 0; i < sectionPointSize; ++i)
{
newVertices[n * sectionPointSize + i] = p.allVectors[i] + (lineDirection * (size * ((deltaRatioxx*i) - 0.5f) ));
newColors[(n * sectionPointSize) + i] = color;
newUV[(n * sectionPointSize) + i] = new Vector2(uvRatio, deltaRatioxx * i);
}
if (n > 0)
{
int triCount = (sectionPointSize-1)*2;
int indexCount = triCount * 3;
for(int k = 0; k < sectionPointSize - 1; ++k)
{
newTriangles[(n - 1) * indexCount + 0+6*k] = ( (n-1) * sectionPointSize) + k;
newTriangles[((n - 1) * indexCount) + 1+6*k] = ((n-1) * sectionPointSize) +1 +k;
newTriangles[((n - 1) * indexCount) + 2+6*k] = (n * sectionPointSize) + k;
newTriangles[((n - 1) * indexCount) + 3+6*k] = (n * sectionPointSize) + 1 + k;
newTriangles[((n - 1) * indexCount) + 4+6*k] = (n * sectionPointSize) + 0 + k;
newTriangles[((n - 1) * indexCount) + 5+6*k] =((n-1) * sectionPointSize) + 1+k;
}
}
}
_trailMesh.Clear();
_trailMesh.vertices = newVertices;
_trailMesh.colors = newColors;
_trailMesh.uv = newUV;
_trailMesh.triangles = newTriangles;
}
}
void RemoveOldPoints(List<Point> pointList)
{
List<Point> remove = new List<Point>();
foreach (Point p in pointList)
{
// cull old points first
if (Time.time - p.timeCreated > _lifeTime)
{
remove.Add(p);
}
}
foreach (Point p in remove)
{
p .RemoveVectors();
pointList.Remove(p);
}
}
}
代码2:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/**
* Interpolation utility functions: easing, bezier, and catmull-rom.
* Consider using Unity's Animation curve editor and AnimationCurve class
* before scripting the desired behaviour using this utility.
*
* Interpolation functionality available at different levels of abstraction.
* Low level access via individual easing functions (ex. EaseInOutCirc),
* Bezier(), and CatmullRom(). High level access using sequence generators,
* NewEase(), NewBezier(), and NewCatmullRom().
*
* Sequence generators are typically used as follows:
*
* IEnumerable<Vector3> sequence = Interpolate.New[Ease|Bezier|CatmulRom](configuration);
* foreach (Vector3 newPoint in sequence) {
* transform.position = newPoint;
* yield return WaitForSeconds(1.0f);
* }
*
* Or:
*
* IEnumerator<Vector3> sequence = Interpolate.New[Ease|Bezier|CatmulRom](configuration).GetEnumerator();
* function Update() {
* if (sequence.MoveNext()) {
* transform.position = sequence.Current;
* }
* }
*
* The low level functions work similarly to Unity's built in Lerp and it is
* up to you to track and pass in elapsedTime and duration on every call. The
* functions take this form (or the logical equivalent for Bezier() and CatmullRom()).
*
* transform.position = ease(start, distance, elapsedTime, duration);
*
* For convenience in configuration you can use the Ease(EaseType) function to
* look up a concrete easing function:
*
* [SerializeField]
* Interpolate.EaseType easeType; // set using Unity's property inspector
* Interpolate.Function ease; // easing of a particular EaseType
* function Awake() {
* ease = Interpolate.Ease(easeType);
* }
*
* @author Fernando Zapata (fernando@cpudreams.com)
* @Traduzione Andrea85cs (andrea85cs@dynematica.it)
*/
public class Interpolate {
/**
* Different methods of easing interpolation.
*/
public enum EaseType {
Linear,
EaseInQuad,
EaseOutQuad,
EaseInOutQuad,
EaseInCubic,
EaseOutCubic,
EaseInOutCubic,
EaseInQuart,
EaseOutQuart,
EaseInOutQuart,
EaseInQuint,
EaseOutQuint,
EaseInOutQuint,
EaseInSine,
EaseOutSine,
EaseInOutSine,
EaseInExpo,
EaseOutExpo,
EaseInOutExpo,
EaseInCirc,
EaseOutCirc,
EaseInOutCirc
}
/**
* Sequence of eleapsedTimes until elapsedTime is >= duration.
*
* Note: elapsedTimes are calculated using the value of Time.deltatTime each
* time a value is requested.
*/
static Vector3 Identity(Vector3 v) {
return v;
}
static Vector3 TransformDotPosition(Transform t) {
return t.position;
}
static IEnumerable<float> NewTimer(float duration) {
float elapsedTime = 0.0f;
while (elapsedTime < duration) {
yield return elapsedTime;
elapsedTime += Time.deltaTime;
// make sure last value is never skipped
if (elapsedTime >= duration) {
yield return elapsedTime;
}
}
}
public delegate Vector3 ToVector3<T>(T v);
public delegate float Function(float a, float b, float c, float d);
/**
* Generates sequence of integers from start to end (inclusive) one step
* at a time.
*/
static IEnumerable<float> NewCounter(int start, int end, int step) {
for (int i = start; i <= end; i += step) {
yield return i;
}
}
/**
* Returns sequence generator from start to end over duration using the
* given easing function. The sequence is generated as it is accessed
* using the Time.deltaTime to calculate the portion of duration that has
* elapsed.
*/
public static IEnumerator NewEase(Function ease, Vector3 start, Vector3 end, float duration) {
IEnumerable<float> timer = Interpolate.NewTimer(duration);
return NewEase(ease, start, end, duration, timer);
}
/**
* Instead of easing based on time, generate n interpolated points (slices)
* between the start and end positions.
*/
public static IEnumerator NewEase(Function ease, Vector3 start, Vector3 end, int slices) {
IEnumerable<float> counter = Interpolate.NewCounter(0, slices + 1, 1);
return NewEase(ease, start, end, slices + 1, counter);
}
/**
* Generic easing sequence generator used to implement the time and
* slice variants. Normally you would not use this function directly.
*/
static IEnumerator NewEase(Function ease, Vector3 start, Vector3 end, float total, IEnumerable<float> driver) {
Vector3 distance = end - start;
foreach (float i in driver) {
yield return Ease(ease, start, distance, i, total);
}
}
/**
* Vector3 interpolation using given easing method. Easing is done independently
* on all three vector axis.
*/
static Vector3 Ease(Function ease, Vector3 start, Vector3 distance, float elapsedTime, float duration) {
start.x = ease(start.x, distance.x, elapsedTime, duration);
start.y = ease(start.y, distance.y, elapsedTime, duration);
start.z = ease(start.z, distance.z, elapsedTime, duration);
return start;
}
/**
* Returns the static method that implements the given easing type for scalars.
* Use this method to easily switch between easing interpolation types.
*
* All easing methods clamp elapsedTime so that it is always <= duration.
*
* var ease = Interpolate.Ease(EaseType.EaseInQuad);
* i = ease(start, distance, elapsedTime, duration);
*/
public static Function Ease(EaseType type) {
// Source Flash easing functions:
// http://gizma.com/easing/
// http://www.robertpenner.com/easing/easing_demo.html
//
// Changed to use more friendly variable names, that follow my Lerp
// conventions:
// start = b (start value)
// distance = c (change in value)
// elapsedTime = t (current time)
// duration = d (time duration)
Function f = null;
switch (type) {
case EaseType.Linear: f = Interpolate.Linear; break;
case EaseType.EaseInQuad: f = Interpolate.EaseInQuad; break;
case EaseType.EaseOutQuad: f = Interpolate.EaseOutQuad; break;
case EaseType.EaseInOutQuad: f = Interpolate.EaseInOutQuad; break;
case EaseType.EaseInCubic: f = Interpolate.EaseInCubic; break;
case EaseType.EaseOutCubic: f = Interpolate.EaseOutCubic; break;
case EaseType.EaseInOutCubic: f = Interpolate.EaseInOutCubic; break;
case EaseType.EaseInQuart: f = Interpolate.EaseInQuart; break;
case EaseType.EaseOutQuart: f = Interpolate.EaseOutQuart; break;
case EaseType.EaseInOutQuart: f = Interpolate.EaseInOutQuart; break;
case EaseType.EaseInQuint: f = Interpolate.EaseInQuint; break;
case EaseType.EaseOutQuint: f = Interpolate.EaseOutQuint; break;
case EaseType.EaseInOutQuint: f = Interpolate.EaseInOutQuint; break;
case EaseType.EaseInSine: f = Interpolate.EaseInSine; break;
case EaseType.EaseOutSine: f = Interpolate.EaseOutSine; break;
case EaseType.EaseInOutSine: f = Interpolate.EaseInOutSine; break;
case EaseType.EaseInExpo: f = Interpolate.EaseInExpo; break;
case EaseType.EaseOutExpo: f = Interpolate.EaseOutExpo; break;
case EaseType.EaseInOutExpo: f = Interpolate.EaseInOutExpo; break;
case EaseType.EaseInCirc: f = Interpolate.EaseInCirc; break;
case EaseType.EaseOutCirc: f = Interpolate.EaseOutCirc; break;
case EaseType.EaseInOutCirc: f = Interpolate.EaseInOutCirc; break;
}
return f;
}
/**
* Returns sequence generator from the first node to the last node over
* duration time using the points in-between the first and last node
* as control points of a bezier curve used to generate the interpolated points
* in the sequence. If there are no control points (ie. only two nodes, first
* and last) then this behaves exactly the same as NewEase(). In other words
* a zero-degree bezier spline curve is just the easing method. The sequence
* is generated as it is accessed using the Time.deltaTime to calculate the
* portion of duration that has elapsed.
*/
public static IEnumerable<Vector3> NewBezier(Function ease, Transform[] nodes, float duration) {
IEnumerable<float> timer = Interpolate.NewTimer(duration);
return NewBezier<Transform>(ease, nodes, TransformDotPosition, duration, timer);
}
/**
* Instead of interpolating based on time, generate n interpolated points
* (slices) between the first and last node.
*/
public static IEnumerable<Vector3> NewBezier(Function ease, Transform[] nodes, int slices) {
IEnumerable<float> counter = NewCounter(0, slices + 1, 1);
return NewBezier<Transform>(ease, nodes, TransformDotPosition, slices + 1, counter);
}
/**
* A Vector3[] variation of the Transform[] NewBezier() function.
* Same functionality but using Vector3s to define bezier curve.
*/
public static IEnumerable<Vector3> NewBezier(Function ease, Vector3[] points, float duration) {
IEnumerable<float> timer = NewTimer(duration);
return NewBezier<Vector3>(ease, points, Identity, duration, timer);
}
/**
* A Vector3[] variation of the Transform[] NewBezier() function.
* Same functionality but using Vector3s to define bezier curve.
*/
public static IEnumerable<Vector3> NewBezier(Function ease, Vector3[] points, int slices) {
IEnumerable<float> counter = NewCounter(0, slices + 1, 1);
return NewBezier<Vector3>(ease, points, Identity, slices + 1, counter);
}
/**
* Generic bezier spline sequence generator used to implement the time and
* slice variants. Normally you would not use this function directly.
*/
static IEnumerable<Vector3> NewBezier<T>(Function ease, IList nodes, ToVector3<T> toVector3, float maxStep, IEnumerable<float> steps) {
// need at least two nodes to spline between
if (nodes.Count >= 2) {
// copy nodes array since Bezier is destructive
Vector3[] points = new Vector3[nodes.Count];
foreach (float step in steps) {
// re-initialize copy before each destructive call to Bezier
for (int i = 0; i < nodes.Count; i++) {
points[i] = toVector3((T)nodes[i]);
}
yield return Bezier(ease, points, step, maxStep);
// make sure last value is always generated
}
}
}
/**
* A Vector3 n-degree bezier spline.
*
* WARNING: The points array is modified by Bezier. See NewBezier() for a
* safe and user friendly alternative.
*
* You can pass zero control points, just the start and end points, for just
* plain easing. In other words a zero-degree bezier spline curve is just the
* easing method.
*
* @param points start point, n control points, end point
*/
static Vector3 Bezier(Function ease, Vector3[] points, float elapsedTime, float duration) {
// Reference: http://ibiblio.org/e-notes/Splines/Bezier.htm
// Interpolate the n starting points to generate the next j = (n - 1) points,
// then interpolate those n - 1 points to generate the next n - 2 points,
// continue this until we have generated the last point (n - (n - 1)), j = 1.
// We store the next set of output points in the same array as the
// input points used to generate them. This works because we store the
// result in the slot of the input point that is no longer used for this
// iteration.
for (int j = points.Length - 1; j > 0; j--) {
for (int i = 0; i < j; i++) {
points[i].x = ease(points[i].x, points[i + 1].x - points[i].x, elapsedTime, duration);
points[i].y = ease(points[i].y, points[i + 1].y - points[i].y, elapsedTime, duration);
points[i].z = ease(points[i].z, points[i + 1].z - points[i].z, elapsedTime, duration);
}
}
return points[0];
}
/**
* Returns sequence generator from the first node, through each control point,
* and to the last node. N points are generated between each node (slices)
* using Catmull-Rom.
*/
public static IEnumerable<Vector3> NewCatmullRom(Transform[] nodes, int slices, bool loop) {
return NewCatmullRom<Transform>(nodes, TransformDotPosition, slices, loop);
}
/**
* A Vector3[] variation of the Transform[] NewCatmullRom() function.
* Same functionality but using Vector3s to define curve.
*/
public static IEnumerable<Vector3> NewCatmullRom(Vector3[] points, int slices, bool loop) {
return NewCatmullRom<Vector3>(points, Identity, slices, loop);
}
/**
* Generic catmull-rom spline sequence generator used to implement the
* Vector3[] and Transform[] variants. Normally you would not use this
* function directly.
*/
static IEnumerable<Vector3> NewCatmullRom<T>(IList nodes, ToVector3<T> toVector3, int slices, bool loop) {
// need at least two nodes to spline between
if (nodes.Count >= 2) {
// yield the first point explicitly, if looping the first point
// will be generated again in the step for loop when interpolating
// from last point back to the first point
yield return toVector3((T)nodes[0]);
int last = nodes.Count - 1;
for (int current = 0; loop || current < last; current++) {
// wrap around when looping
if (loop && current > last) {
current = 0;
}
// handle edge cases for looping and non-looping scenarios
// when looping we wrap around, when not looping use start for previous
// and end for next when you at the ends of the nodes array
int previous = (current == 0) ? ((loop) ? last : current) : current - 1;
int start = current;
int end = (current == last) ? ((loop) ? 0 : current) : current + 1;
int next = (end == last) ? ((loop) ? 0 : end) : end + 1;
// adding one guarantees yielding at least the end point
int stepCount = slices + 1;
for (int step = 1; step <= stepCount; step++) {
yield return CatmullRom(toVector3((T)nodes[previous]),
toVector3((T)nodes[start]),
toVector3((T)nodes[end]),
toVector3((T)nodes[next]),
step, stepCount);
}
}
}
}
/**
* A Vector3 Catmull-Rom spline. Catmull-Rom splines are similar to bezier
* splines but have the useful property that the generated curve will go
* through each of the control points.
*
* NOTE: The NewCatmullRom() functions are an easier to use alternative to this
* raw Catmull-Rom implementation.
*
* @param previous the point just before the start point or the start point
* itself if no previous point is available
* @param start generated when elapsedTime == 0
* @param end generated when elapsedTime >= duration
* @param next the point just after the end point or the end point itself if no
* next point is available
*/
static Vector3 CatmullRom(Vector3 previous, Vector3 start, Vector3 end, Vector3 next,
float elapsedTime, float duration) {
// References used:
// p.266 GemsV1
//
// tension is often set to 0.5 but you can use any reasonable value:
// http://www.cs.cmu.edu/~462/projects/assn2/assn2/catmullRom.pdf
//
// bias and tension controls:
// http://local.wasp.uwa.edu.au/~pbourke/miscellaneous/interpolation/
float percentComplete = elapsedTime / duration;
float percentCompleteSquared = percentComplete * percentComplete;
float percentCompleteCubed = percentCompleteSquared * percentComplete;
return previous * (-0.5f * percentCompleteCubed +
percentCompleteSquared -
0.5f * percentComplete) +
start * ( 1.5f * percentCompleteCubed +
-2.5f * percentCompleteSquared + 1.0f) +
end * (-1.5f * percentCompleteCubed +
2.0f * percentCompleteSquared +
0.5f * percentComplete) +
next * ( 0.5f * percentCompleteCubed -
0.5f * percentCompleteSquared);
}
/**
* Linear interpolation (same as Mathf.Lerp)
*/
static float Linear(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime to be <= duration
if (elapsedTime > duration) { elapsedTime = duration; }
return distance * (elapsedTime / duration) + start;
}
/**
* quadratic easing in - accelerating from zero velocity
*/
static float EaseInQuad(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime so that it cannot be greater than duration
elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
return distance * elapsedTime * elapsedTime + start;
}
/**
* quadratic easing out - decelerating to zero velocity
*/
static float EaseOutQuad(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime so that it cannot be greater than duration
elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
return -distance * elapsedTime * (elapsedTime - 2) + start;
}
/**
* quadratic easing in/out - acceleration until halfway, then deceleration
*/
static float EaseInOutQuad(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime so that it cannot be greater than duration
elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2);
if (elapsedTime < 1) return distance / 2 * elapsedTime * elapsedTime + start;
elapsedTime--;
return -distance / 2 * (elapsedTime * (elapsedTime - 2) - 1) + start;
}
/**
* cubic easing in - accelerating from zero velocity
*/
static float EaseInCubic(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime so that it cannot be greater than duration
elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
return distance * elapsedTime * elapsedTime * elapsedTime + start;
}
/**
* cubic easing out - decelerating to zero velocity
*/
static float EaseOutCubic(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime so that it cannot be greater than duration
elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
elapsedTime--;
return distance * (elapsedTime * elapsedTime * elapsedTime + 1) + start;
}
/**
* cubic easing in/out - acceleration until halfway, then deceleration
*/
static float EaseInOutCubic(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime so that it cannot be greater than duration
elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2);
if (elapsedTime < 1) return distance / 2 * elapsedTime * elapsedTime * elapsedTime + start;
elapsedTime -= 2;
return distance / 2 * (elapsedTime * elapsedTime * elapsedTime + 2) + start;
}
/**
* quartic easing in - accelerating from zero velocity
*/
static float EaseInQuart(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime so that it cannot be greater than duration
elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
return distance * elapsedTime * elapsedTime * elapsedTime * elapsedTime + start;
}
/**
* quartic easing out - decelerating to zero velocity
*/
static float EaseOutQuart(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime so that it cannot be greater than duration
elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
elapsedTime--;
return -distance * (elapsedTime * elapsedTime * elapsedTime * elapsedTime - 1) + start;
}
/**
* quartic easing in/out - acceleration until halfway, then deceleration
*/
static float EaseInOutQuart(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime so that it cannot be greater than duration
elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2);
if (elapsedTime < 1) return distance / 2 * elapsedTime * elapsedTime * elapsedTime * elapsedTime + start;
elapsedTime -= 2;
return -distance / 2 * (elapsedTime * elapsedTime * elapsedTime * elapsedTime - 2) + start;
}
/**
* quintic easing in - accelerating from zero velocity
*/
static float EaseInQuint(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime so that it cannot be greater than duration
elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
return distance * elapsedTime * elapsedTime * elapsedTime * elapsedTime * elapsedTime + start;
}
/**
* quintic easing out - decelerating to zero velocity
*/
static float EaseOutQuint(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime so that it cannot be greater than duration
elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
elapsedTime--;
return distance * (elapsedTime * elapsedTime * elapsedTime * elapsedTime * elapsedTime + 1) + start;
}
/**
* quintic easing in/out - acceleration until halfway, then deceleration
*/
static float EaseInOutQuint(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime so that it cannot be greater than duration
elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2f);
if (elapsedTime < 1) return distance / 2 * elapsedTime * elapsedTime * elapsedTime * elapsedTime * elapsedTime + start;
elapsedTime -= 2;
return distance / 2 * (elapsedTime * elapsedTime * elapsedTime * elapsedTime * elapsedTime + 2) + start;
}
/**
* sinusoidal easing in - accelerating from zero velocity
*/
static float EaseInSine(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime to be <= duration
if (elapsedTime > duration) { elapsedTime = duration; }
return -distance * Mathf.Cos(elapsedTime / duration * (Mathf.PI / 2)) + distance + start;
}
/**
* sinusoidal easing out - decelerating to zero velocity
*/
static float EaseOutSine(float start, float distance, float elapsedTime, float duration) {
if (elapsedTime > duration) { elapsedTime = duration; }
return distance * Mathf.Sin(elapsedTime / duration * (Mathf.PI / 2)) + start;
}
/**
* sinusoidal easing in/out - accelerating until halfway, then decelerating
*/
static float EaseInOutSine(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime to be <= duration
if (elapsedTime > duration) { elapsedTime = duration; }
return -distance / 2 * (Mathf.Cos(Mathf.PI * elapsedTime / duration) - 1) + start;
}
/**
* exponential easing in - accelerating from zero velocity
*/
static float EaseInExpo(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime to be <= duration
if (elapsedTime > duration) { elapsedTime = duration; }
return distance * Mathf.Pow(2, 10 * (elapsedTime / duration - 1)) + start;
}
/**
* exponential easing out - decelerating to zero velocity
*/
static float EaseOutExpo(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime to be <= duration
if (elapsedTime > duration) { elapsedTime = duration; }
return distance * (-Mathf.Pow(2, -10 * elapsedTime / duration) + 1) + start;
}
/**
* exponential easing in/out - accelerating until halfway, then decelerating
*/
static float EaseInOutExpo(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime so that it cannot be greater than duration
elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2);
if (elapsedTime < 1) return distance / 2 * Mathf.Pow(2, 10 * (elapsedTime - 1)) + start;
elapsedTime--;
return distance / 2 * (-Mathf.Pow(2, -10 * elapsedTime) + 2) + start;
}
/**
* circular easing in - accelerating from zero velocity
*/
static float EaseInCirc(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime so that it cannot be greater than duration
elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
return -distance * (Mathf.Sqrt(1 - elapsedTime * elapsedTime) - 1) + start;
}
/**
* circular easing out - decelerating to zero velocity
*/
static float EaseOutCirc(float start, float distance, float elapsedTime, float duration) {
// clamp elapsedTime so that it cannot be greater than duration
elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
elapsedTime--;
return distance * Mathf.Sqrt(1 - elapsedTime * elapsedTime) + start;
}
/**
* circular easing in/out - acceleration until halfway, then deceleration
*/
static float EaseInOutCirc(float start, float distance, float
elapsedTime, float duration) {
// clamp elapsedTime so that it cannot be greater than duration
elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2);
if (elapsedTime < 1) return -distance / 2 * (Mathf.Sqrt(1 - elapsedTime * elapsedTime) - 1) + start;
elapsedTime -= 2;
return distance / 2 * (Mathf.Sqrt(1 - elapsedTime * elapsedTime) + 1) + start;
}
}