rabidus-test/Assets/Dreamteck/Splines/Components/SplineComputer.cs

2162 lines
74 KiB
C#
Raw Permalink Normal View History

2023-07-24 16:38:13 +03:00
namespace Dreamteck.Splines
{
using UnityEngine;
using System.Collections.Generic;
public delegate void EmptySplineHandler();
//MonoBehaviour wrapper for the spline class. It transforms the spline using the object's transform and provides thread-safe methods for sampling
[AddComponentMenu("Dreamteck/Splines/Spline Computer")]
[ExecuteInEditMode]
public partial class SplineComputer : MonoBehaviour
{
#if UNITY_EDITOR
public enum EditorUpdateMode { Default, OnMouseUp }
[HideInInspector]
public bool editorDrawPivot = true;
[HideInInspector]
public Color editorPathColor = Color.white;
[HideInInspector]
public bool editorAlwaysDraw = false;
[HideInInspector]
public bool editorDrawThickness = false;
[HideInInspector]
public bool editorBillboardThickness = true;
private bool _editorIsPlaying = false;
[HideInInspector]
public bool isNewlyCreated = true;
[HideInInspector]
public EditorUpdateMode editorUpdateMode = EditorUpdateMode.Default;
#endif
public enum Space { World, Local };
public enum EvaluateMode { Cached, Calculate }
public enum SampleMode { Default, Uniform, Optimized }
public enum UpdateMode { Update, FixedUpdate, LateUpdate, AllUpdate, None }
public Space space
{
get { return _space; }
set
{
if (value != _space)
{
SplinePoint[] worldPoints = GetPoints();
_space = value;
SetPoints(worldPoints);
}
}
}
public Spline.Type type
{
get
{
return _spline.type;
}
set
{
if (value != _spline.type)
{
_spline.type = value;
Rebuild(true);
}
}
}
public float knotParametrization
{
get { return _spline.knotParametrization; }
set
{
float last = _spline.knotParametrization;
_spline.knotParametrization = value;
if(last != _spline.knotParametrization)
{
Rebuild(true);
}
}
}
public bool linearAverageDirection
{
get
{
return _spline.linearAverageDirection;
}
set
{
if (value != _spline.linearAverageDirection)
{
_spline.linearAverageDirection = value;
Rebuild(true);
}
}
}
public bool is2D
{
get { return _is2D; }
set
{
if (value != _is2D)
{
_is2D = value;
SetPoints(GetPoints());
}
}
}
public int sampleRate
{
get { return _spline.sampleRate; }
set
{
if (value != _spline.sampleRate)
{
if (value < 2) value = 2;
_spline.sampleRate = value;
Rebuild(true);
}
}
}
public float optimizeAngleThreshold
{
get { return _optimizeAngleThreshold; }
set
{
if (value != _optimizeAngleThreshold)
{
if (value < 0.001f) value = 0.001f;
_optimizeAngleThreshold = value;
if (_sampleMode == SampleMode.Optimized)
{
Rebuild(true);
}
}
}
}
public SampleMode sampleMode
{
get { return _sampleMode; }
set
{
if (value != _sampleMode)
{
_sampleMode = value;
Rebuild(true);
}
}
}
[HideInInspector]
public bool multithreaded = false;
[HideInInspector]
public UpdateMode updateMode = UpdateMode.Update;
[HideInInspector]
public TriggerGroup[] triggerGroups = new TriggerGroup[0];
public AnimationCurve customValueInterpolation
{
get { return _spline.customValueInterpolation; }
set
{
_spline.customValueInterpolation = value;
Rebuild();
}
}
public AnimationCurve customNormalInterpolation
{
get { return _spline.customNormalInterpolation; }
set
{
_spline.customNormalInterpolation = value;
Rebuild();
}
}
public int iterations
{
get
{
return _spline.iterations;
}
}
public double moveStep
{
get
{
return _spline.moveStep;
}
}
public bool isClosed
{
get
{
return _spline.isClosed;
}
}
public int pointCount
{
get
{
return _spline.points.Length;
}
}
public int sampleCount
{
get { return _sampleCollection.length; }
}
/// <summary>
/// Returns the sample at the index transformed by the object's matrix
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public SplineSample this [int index]
{
get
{
UpdateSampleCollection();
return _sampleCollection.samples[index];
}
}
/// <summary>
/// The raw spline samples without transformation applied
/// </summary>
public SplineSample[] rawSamples
{
get { return _rawSamples; }
}
/// <summary>
/// Thread-safe transform's position
/// </summary>
public Vector3 position
{
get {
#if UNITY_EDITOR
if (!_editorIsPlaying) return transform.position;
#endif
return _localToWorldMatrix.MultiplyPoint3x4(Vector3.zero);
}
}
/// <summary>
/// Thread-safe transform's rotation
/// </summary>
public Quaternion rotation
{
get {
#if UNITY_EDITOR
if (!_editorIsPlaying) return transform.rotation;
#endif
return _localToWorldMatrix.rotation;
}
}
/// <summary>
/// Thread-safe transform's scale
/// </summary>
public Vector3 scale
{
get {
#if UNITY_EDITOR
if (!_editorIsPlaying) return transform.lossyScale;
#endif
return _localToWorldMatrix.lossyScale;
}
}
/// <summary>
/// returns the number of subscribers this computer has
/// </summary>
public int subscriberCount
{
get
{
return _subscribers.Length;
}
}
[HideInInspector]
[SerializeField]
[UnityEngine.Serialization.FormerlySerializedAs("spline")]
private Spline _spline = new Spline(Spline.Type.CatmullRom);
[HideInInspector]
private SampleCollection _sampleCollection = new SampleCollection();
[HideInInspector]
[SerializeField]
[UnityEngine.Serialization.FormerlySerializedAs("originalSamplePercents")]
private double[] _originalSamplePercents = new double[0];
[HideInInspector]
[SerializeField]
private bool _is2D = false;
[HideInInspector]
[SerializeField]
private bool hasSamples = false;
[HideInInspector]
[SerializeField]
[Range(0.001f, 45f)]
private float _optimizeAngleThreshold = 0.5f;
[HideInInspector]
[SerializeField]
private Space _space = Space.Local;
[HideInInspector]
[SerializeField]
private SampleMode _sampleMode = SampleMode.Default;
[HideInInspector]
[SerializeField]
private SplineUser[] _subscribers = new SplineUser[0];
[HideInInspector]
[SerializeField]
private SplineSample[] _rawSamples = new SplineSample[0];
private Matrix4x4 _localToWorldMatrix = Matrix4x4.identity;
private Matrix4x4 _worldToLocalMatrix = Matrix4x4.identity;
private Matrix4x4 _localToWorldRotationMatrix = Matrix4x4.identity;
[HideInInspector]
[SerializeField]
[UnityEngine.Serialization.FormerlySerializedAs("nodes")]
private NodeLink[] _nodes = new NodeLink[0];
private bool _rebuildPending = false;
private bool _trsCached = false;
private Transform _trs = null;
public Transform trs
{
get
{
#if UNITY_EDITOR
if (!_editorIsPlaying)
{
return transform;
}
#endif
if (!_trsCached)
{
_trs = transform;
_trsCached = true;
}
return _trs;
}
}
private bool _queueResample = false, _queueRebuild = false;
public event EmptySplineHandler onRebuild;
private bool useMultithreading
{
get
{
return multithreaded
#if UNITY_EDITOR
&& _editorIsPlaying
#endif
;
}
}
#if UNITY_EDITOR
/// <summary>
/// Used by the editor - should not be called from the API
/// </summary>
public void EditorAwake()
{
UpdateConnectedNodes();
RebuildImmediate(true, true);
}
/// <summary>
/// Used by the editor - should not be called from the API
/// </summary>
public void EditorUpdateConnectedNodes()
{
UpdateConnectedNodes();
}
#endif
private void Awake()
{
#if UNITY_EDITOR
_editorIsPlaying = Application.isPlaying;
#endif
ResampleTransform();
}
void FixedUpdate()
{
if (updateMode == UpdateMode.FixedUpdate || updateMode == UpdateMode.AllUpdate)
{
RunUpdate();
}
}
void LateUpdate()
{
if (updateMode == UpdateMode.LateUpdate || updateMode == UpdateMode.AllUpdate)
{
RunUpdate();
}
}
void Update()
{
if (updateMode == UpdateMode.Update || updateMode == UpdateMode.AllUpdate)
{
RunUpdate();
}
}
private void RunUpdate(bool immediate = false)
{
bool transformChanged = ResampleTransformIfNeeded();
if(_sampleCollection.samples.Length != _rawSamples.Length)
{
transformChanged = true;
}
if (useMultithreading)
{
//Rebuild users at the beginning of the next cycle if multithreaded
if (_queueRebuild)
{
RebuildUsers(immediate);
}
}
if (_queueResample)
{
if (useMultithreading)
{
if (transformChanged)
{
SplineThreading.Run(CalculateWithoutTransform);
} else
{
SplineThreading.Run(CalculateWithTransform);
}
}
else
{
CalculateSamples(!transformChanged);
}
}
if (transformChanged)
{
if (useMultithreading)
{
SplineThreading.Run(TransformSamples);
}
else
{
TransformSamples();
}
}
if (!useMultithreading)
{
//If not multithreaded, rebuild users here
if (_queueRebuild)
{
RebuildUsers(immediate);
}
}
void CalculateWithTransform()
{
CalculateSamples();
}
void CalculateWithoutTransform()
{
CalculateSamples(false);
}
}
#if UNITY_EDITOR
private void Reset()
{
editorPathColor = SplinePrefs.defaultColor;
editorDrawThickness = SplinePrefs.defaultShowThickness;
is2D = SplinePrefs.default2D;
editorAlwaysDraw = SplinePrefs.defaultAlwaysDraw;
editorUpdateMode = SplinePrefs.defaultEditorUpdateMode;
space = SplinePrefs.defaultComputerSpace;
type = SplinePrefs.defaultType;
}
#endif
void OnEnable()
{
if (_rebuildPending)
{
_rebuildPending = false;
Rebuild();
}
}
public void GetSamples(SampleCollection collection)
{
UpdateSampleCollection();
collection.samples = _sampleCollection.samples;
collection.optimizedIndices = _sampleCollection.optimizedIndices;
collection.sampleMode = _sampleMode;
}
private void UpdateSampleCollection()
{
if (_sampleCollection.samples.Length != _rawSamples.Length)
{
TransformSamples();
}
}
private bool ResampleTransformIfNeeded()
{
bool changed = false;
//This is used to skip comparing matrices on every frame during runtime
#if UNITY_EDITOR
if (_editorIsPlaying)
{
#endif
if (!trs.hasChanged) return false;
trs.hasChanged = false;
#if UNITY_EDITOR
}
#endif
if (_localToWorldMatrix != trs.localToWorldMatrix)
{
ResampleTransform();
_queueRebuild = true;
changed = true;
}
return changed;
}
/// <summary>
/// Immediately sample the computer's transform (thread-unsafe). Call this before SetPoint(s) if the transform has been modified in the same frame
/// </summary>
public void ResampleTransform()
{
_localToWorldMatrix = trs.localToWorldMatrix;
_localToWorldRotationMatrix = Matrix4x4.TRS(Vector3.zero, trs.rotation, Vector3.one);
_worldToLocalMatrix = trs.worldToLocalMatrix;
}
/// <summary>
/// Subscribe a SplineUser to this computer. This will rebuild the user automatically when there are changes.
/// </summary>
/// <param name="input">The SplineUser to subscribe</param>
public void Subscribe(SplineUser input)
{
if (!IsSubscribed(input))
{
ArrayUtility.Add(ref _subscribers, input);
}
}
/// <summary>
/// Unsubscribe a SplineUser from this computer's updates
/// </summary>
/// <param name="input">The SplineUser to unsubscribe</param>
public void Unsubscribe(SplineUser input)
{
for (int i = 0; i < _subscribers.Length; i++)
{
if (_subscribers[i] == input)
{
ArrayUtility.RemoveAt(ref _subscribers, i);
return;
}
}
}
/// <summary>
/// Checks if a user is subscribed to that computer
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public bool IsSubscribed(SplineUser user)
{
for (int i = 0; i < _subscribers.Length; i++)
{
if (_subscribers[i] == user)
{
return true;
}
}
return false;
}
/// <summary>
/// Returns an array of subscribed users
/// </summary>
/// <returns></returns>
public SplineUser[] GetSubscribers()
{
SplineUser[] subs = new SplineUser[_subscribers.Length];
_subscribers.CopyTo(subs, 0);
return subs;
}
/// <summary>
/// Get the points from this computer's spline. All points are transformed in world coordinates.
/// </summary>
/// <returns></returns>
public SplinePoint[] GetPoints(Space getSpace = Space.World)
{
SplinePoint[] points = new SplinePoint[_spline.points.Length];
for (int i = 0; i < points.Length; i++)
{
points[i] = _spline.points[i];
if (_space == Space.Local && getSpace == Space.World)
{
points[i].position = TransformPoint(points[i].position);
points[i].tangent = TransformPoint(points[i].tangent);
points[i].tangent2 = TransformPoint(points[i].tangent2);
points[i].normal = TransformDirection(points[i].normal);
}
}
return points;
}
/// <summary>
/// Get a point from this computer's spline. The point is transformed in world coordinates.
/// </summary>
/// <param name="index">Point index</param>
/// <returns></returns>
public SplinePoint GetPoint(int index, Space getSpace = Space.World)
{
if (index < 0 || index >= _spline.points.Length) return new SplinePoint();
if (_space == Space.Local && getSpace == Space.World)
{
ResampleTransformIfNeeded();
SplinePoint point = _spline.points[index];
point.position = TransformPoint(point.position);
point.tangent = TransformPoint(point.tangent);
point.tangent2 = TransformPoint(point.tangent2);
point.normal = TransformDirection(point.normal);
return point;
}
else
{
return _spline.points[index];
}
}
public Vector3 GetPointPosition(int index, Space getSpace = Space.World)
{
if (_space == Space.Local && getSpace == Space.World)
{
ResampleTransformIfNeeded();
return TransformPoint(_spline.points[index].position);
}
else return _spline.points[index].position;
}
public Vector3 GetPointNormal(int index, Space getSpace = Space.World)
{
if (_space == Space.Local && getSpace == Space.World)
{
ResampleTransformIfNeeded();
return TransformDirection(_spline.points[index].normal).normalized;
}
else return _spline.points[index].normal;
}
public Vector3 GetPointTangent(int index, Space getSpace = Space.World)
{
if (_space == Space.Local && getSpace == Space.World)
{
ResampleTransformIfNeeded();
return TransformPoint(_spline.points[index].tangent);
}
else return _spline.points[index].tangent;
}
public Vector3 GetPointTangent2(int index, Space getSpace = Space.World)
{
if (_space == Space.Local && getSpace == Space.World)
{
ResampleTransformIfNeeded();
return TransformPoint(_spline.points[index].tangent2);
}
else return _spline.points[index].tangent2;
}
public float GetPointSize(int index, Space getSpace = Space.World)
{
return _spline.points[index].size;
}
public Color GetPointColor(int index, Space getSpace = Space.World)
{
return _spline.points[index].color;
}
private void Make2D(ref SplinePoint point)
{
point.Flatten(LinearAlgebraUtility.Axis.Z);
}
/// <summary>
/// Set the points of this computer's spline.
/// </summary>
/// <param name="points">The points array</param>
/// <param name="setSpace">Use world or local space</param>
public void SetPoints(SplinePoint[] points, Space setSpace = Space.World)
{
ResampleTransformIfNeeded();
bool rebuild = false;
if (points.Length != _spline.points.Length)
{
rebuild = true;
if (points.Length < 3)
{
Break();
}
_spline.points = new SplinePoint[points.Length];
SetAllDirty();
}
for (int i = 0; i < points.Length; i++)
{
SplinePoint newPoint = points[i];
if(_spline.points.Length > i)
{
newPoint.isDirty = _spline.points[i].isDirty;
}
if (_space == Space.Local && setSpace == Space.World)
{
newPoint.position = InverseTransformPoint(points[i].position);
newPoint.tangent = InverseTransformPoint(points[i].tangent);
newPoint.tangent2 = InverseTransformPoint(points[i].tangent2);
newPoint.normal = InverseTransformDirection(points[i].normal);
}
if (_is2D)
{
Make2D(ref newPoint);
}
if (newPoint != _spline.points[i])
{
newPoint.isDirty = true;
rebuild = true;
}
_spline.points[i] = newPoint;
}
if (rebuild)
{
Rebuild();
UpdateConnectedNodes(points);
}
}
/// <summary>
/// Set the position of a control point. This is faster than SetPoint
/// </summary>
/// <param name="index"></param>
/// <param name="pos"></param>
/// <param name="setSpace"></param>
public void SetPointPosition(int index, Vector3 pos, Space setSpace = Space.World)
{
if (index < 0) return;
ResampleTransformIfNeeded();
if (index >= _spline.points.Length)
{
AppendPoints((index + 1) - _spline.points.Length);
}
Vector3 newPos = pos;
if (_space == Space.Local && setSpace == Space.World) newPos = InverseTransformPoint(pos);
if (newPos != _spline.points[index].position)
{
SetDirty(index);
_spline.points[index].SetPosition(newPos);
Rebuild();
SetNodeForPoint(index, GetPoint(index));
}
}
/// <summary>
/// Set the tangents of a control point. This is faster than SetPoint
/// </summary>
/// <param name="index"></param>
/// <param name="tan1"></param>
/// <param name="tan2"></param>
/// <param name="setSpace"></param>
public void SetPointTangents(int index, Vector3 tan1, Vector3 tan2, Space setSpace = Space.World)
{
if (index < 0) return;
ResampleTransformIfNeeded();
if (index >= _spline.points.Length)
{
AppendPoints((index + 1) - _spline.points.Length);
}
Vector3 newTan1 = tan1;
Vector3 newTan2 = tan2;
if (_space == Space.Local && setSpace == Space.World)
{
newTan1 = InverseTransformPoint(tan1);
newTan2 = InverseTransformPoint(tan2);
}
bool rebuild = false;
if (newTan2 != _spline.points[index].tangent2)
{
rebuild = true;
_spline.points[index].SetTangent2Position(newTan2);
}
if (newTan1 != _spline.points[index].tangent)
{
rebuild = true;
_spline.points[index].SetTangentPosition(newTan1);
}
if (_is2D) Make2D(ref _spline.points[index]);
if (rebuild)
{
SetDirty(index);
Rebuild();
SetNodeForPoint(index, GetPoint(index));
}
}
/// <summary>
/// Set the normal of a control point. This is faster than SetPoint
/// </summary>
/// <param name="index"></param>
/// <param name="nrm"></param>
/// <param name="setSpace"></param>
public void SetPointNormal(int index, Vector3 nrm, Space setSpace = Space.World)
{
if (index < 0) return;
ResampleTransformIfNeeded();
if (index >= _spline.points.Length)
{
AppendPoints((index + 1) - _spline.points.Length);
}
Vector3 newNrm = nrm;
if (_space == Space.Local && setSpace == Space.World) newNrm = InverseTransformDirection(nrm);
if (newNrm != _spline.points[index].normal)
{
SetDirty(index);
_spline.points[index].normal = newNrm;
if (_is2D) Make2D(ref _spline.points[index]);
Rebuild();
SetNodeForPoint(index, GetPoint(index));
}
}
/// <summary>
/// Set the size of a control point. This is faster than SetPoint
/// </summary>
/// <param name="index"></param>
/// <param name="size"></param>
public void SetPointSize(int index, float size)
{
if (index < 0) return;
if (index >= _spline.points.Length)
{
AppendPoints((index + 1) - _spline.points.Length);
}
if (size != _spline.points[index].size)
{
SetDirty(index);
_spline.points[index].size = size;
Rebuild();
SetNodeForPoint(index, GetPoint(index));
}
}
/// <summary>
/// Set the color of a control point. THis is faster than SetPoint
/// </summary>
/// <param name="index"></param>
/// <param name="color"></param>
public void SetPointColor(int index, Color color)
{
if (index < 0) return;
if (index >= _spline.points.Length)
{
AppendPoints((index + 1) - _spline.points.Length);
}
if (color != _spline.points[index].color)
{
SetDirty(index);
_spline.points[index].color = color;
Rebuild();
SetNodeForPoint(index, GetPoint(index));
}
}
/// <summary>
/// Set a control point in world coordinates
/// </summary>
/// <param name="index"></param>
/// <param name="point"></param>
public void SetPoint(int index, SplinePoint point, Space setSpace = Space.World)
{
if (index < 0) return;
ResampleTransformIfNeeded();
if (index >= _spline.points.Length)
{
AppendPoints((index + 1) - _spline.points.Length);
}
SplinePoint newPoint = point;
if (_space == Space.Local && setSpace == Space.World)
{
newPoint.position = InverseTransformPoint(point.position);
newPoint.tangent = InverseTransformPoint(point.tangent);
newPoint.tangent2 = InverseTransformPoint(point.tangent2);
newPoint.normal = InverseTransformDirection(point.normal);
}
if (_is2D)
{
Make2D(ref newPoint);
}
if (newPoint != _spline.points[index])
{
newPoint.isDirty = true;
_spline.points[index] = newPoint;
Rebuild();
SetNodeForPoint(index, point);
}
}
private void AppendPoints(int count)
{
SplinePoint[] newPoints = new SplinePoint[_spline.points.Length + count];
_spline.points.CopyTo(newPoints, 0);
_spline.points = newPoints;
Rebuild(true);
}
/// <summary>
/// Converts a point index to spline percent
/// </summary>
/// <param name="pointIndex">The point index</param>
/// <returns></returns>
public double GetPointPercent(int pointIndex)
{
double percent = DMath.Clamp01((double)pointIndex / (_spline.points.Length - 1));
if (_spline.isClosed)
{
percent = DMath.Clamp01((double)pointIndex / _spline.points.Length);
}
if (_sampleMode != SampleMode.Uniform) return percent;
if (_originalSamplePercents.Length <= 1) return 0.0;
for (int i = _originalSamplePercents.Length - 2; i >= 0; i--)
{
if (_originalSamplePercents[i] < percent)
{
double inverseLerp = DMath.InverseLerp(_originalSamplePercents[i], _originalSamplePercents[i + 1], percent);
return DMath.Lerp(_rawSamples[i].percent, _rawSamples[i+1].percent, inverseLerp);
}
}
return 0.0;
}
public int PercentToPointIndex(double percent, Spline.Direction direction = Spline.Direction.Forward)
{
int count = _spline.points.Length - 1;
if (isClosed) count = _spline.points.Length;
if (_sampleMode == SampleMode.Uniform)
{
int index;
double lerp;
GetSamplingValues(percent, out index, out lerp);
if (lerp > 0.0 && index < _originalSamplePercents.Length - 1)
{
lerp = DMath.Lerp(_originalSamplePercents[index], _originalSamplePercents[index + 1], lerp);
if (direction == Spline.Direction.Forward)
{
return DMath.FloorInt(lerp * count);
}
else
{
return DMath.CeilInt(lerp * count);
}
}
if (direction == Spline.Direction.Forward)
{
return DMath.FloorInt(_originalSamplePercents[index] * count);
}
else
{
return DMath.CeilInt(_originalSamplePercents[index] * count);
}
}
int point = 0;
if (direction == Spline.Direction.Forward)
{
point = DMath.FloorInt(percent * count);
}
else
{
point = DMath.CeilInt(percent * count);
}
if (point >= _spline.points.Length)
{
point = 0;
}
return point;
}
public Vector3 EvaluatePosition(double percent)
{
return EvaluatePosition(percent, EvaluateMode.Cached);
}
/// <summary>
/// Same as Spline.EvaluatePosition but the result is transformed by the computer's transform
/// </summary>
/// <param name="percent">Evaluation percent</param>
/// <param name="mode">Mode to use the method in. Cached uses the cached samples while Calculate is more accurate but heavier</param>
/// <returns></returns>
public Vector3 EvaluatePosition(double percent, EvaluateMode mode = EvaluateMode.Cached)
{
if (mode == EvaluateMode.Calculate) return TransformPoint(_spline.EvaluatePosition(percent));
UpdateSampleCollection();
return _sampleCollection.EvaluatePosition(percent);
}
public Vector3 EvaluatePosition(int pointIndex, EvaluateMode mode = EvaluateMode.Cached)
{
return EvaluatePosition(GetPointPercent(pointIndex), mode);
}
public SplineSample Evaluate(double percent)
{
return Evaluate(percent, EvaluateMode.Cached);
}
/// <summary>
/// Same as Spline.Evaluate but the result is transformed by the computer's transform
/// </summary>
/// <param name="percent">Evaluation percent</param>
/// <param name="mode">Mode to use the method in. Cached uses the cached samples while Calculate is more accurate but heavier</param>
/// <returns></returns>
public SplineSample Evaluate(double percent, EvaluateMode mode = EvaluateMode.Cached)
{
SplineSample result = new SplineSample();
Evaluate(percent, ref result, mode);
return result;
}
/// <summary>
/// Evaluate the spline at the position of a given point and return a SplineSample
/// </summary>
/// <param name="pointIndex">Point index</param>
/// <param name="mode">Mode to use the method in. Cached uses the cached samples while Calculate is more accurate but heavier</param>
public SplineSample Evaluate(int pointIndex)
{
SplineSample result = new SplineSample();
Evaluate(pointIndex, ref result);
return result;
}
/// <summary>
/// Evaluate the spline at the position of a given point and write in the SplineSample output
/// </summary>
/// <param name="pointIndex">Point index</param>
public void Evaluate(int pointIndex, ref SplineSample result)
{
Evaluate(GetPointPercent(pointIndex), ref result);
}
public void Evaluate(double percent, ref SplineSample result)
{
Evaluate(percent, ref result, EvaluateMode.Cached);
}
/// <summary>
/// Same as Spline.Evaluate but the result is transformed by the computer's transform
/// </summary>
/// <param name="result"></param>
/// <param name="percent"></param>
public void Evaluate(double percent, ref SplineSample result, EvaluateMode mode = EvaluateMode.Cached)
{
if (mode == EvaluateMode.Calculate)
{
_spline.Evaluate(percent, ref result);
TransformSample(ref result);
} else
{
UpdateSampleCollection();
_sampleCollection.Evaluate(percent, ref result);
}
}
/// <summary>
/// Same as Spline.Evaluate but the results are transformed by the computer's transform
/// </summary>
/// <param name="from">Start position [0-1]</param>
/// <param name="to">Target position [from-1]</param>
/// <returns></returns>
public void Evaluate(ref SplineSample[] results, double from = 0.0, double to = 1.0)
{
UpdateSampleCollection();
_sampleCollection.Evaluate(ref results, from, to);
}
/// <summary>
/// Same as Spline.EvaluatePositions but the results are transformed by the computer's transform
/// </summary>
/// <param name="from">Start position [0-1]</param>
/// <param name="to">Target position [from-1]</param>
/// <returns></returns>
public void EvaluatePositions(ref Vector3[] positions, double from = 0.0, double to = 1.0)
{
UpdateSampleCollection();
_sampleCollection.EvaluatePositions(ref positions, from, to);
}
/// <summary>
/// Returns the percent from the spline at a given distance from the start point
/// </summary>
/// <param name="start">The start point</param>
/// /// <param name="distance">The distance to travel</param>
/// <param name="direction">The direction towards which to move</param>
/// <returns></returns>
public double Travel(double start, float distance, out float moved, Spline.Direction direction = Spline.Direction.Forward)
{
UpdateSampleCollection();
return _sampleCollection.Travel(start, distance, direction, out moved);
}
public double Travel(double start, float distance, Spline.Direction direction = Spline.Direction.Forward)
{
float moved;
return Travel(start, distance, out moved, direction);
}
[System.Obsolete("This project override is obsolete, please use Project(Vector3 position, ref SplineSample result, double from = 0.0, double to = 1.0, EvaluateMode mode = EvaluateMode.Cached, int subdivisions = 4) instead")]
public void Project(ref SplineSample result, Vector3 position, double from = 0.0, double to = 1.0, EvaluateMode mode = EvaluateMode.Cached, int subdivisions = 4)
{
Project(position, ref result, from, to, mode, subdivisions);
}
/// <summary>
/// Same as Spline.Project but the point is transformed by the computer's transform.
/// </summary>
/// <param name="worldPoint">Point in world space</param>
/// <param name="subdivide">Subdivisions default: 4</param>
/// <param name="from">Sample from [0-1] default: 0f</param>
/// <param name="to">Sample to [0-1] default: 1f</param>
/// <param name="mode">Mode to use the method in. Cached uses the cached samples while Calculate is more accurate but heavier</param>
/// <param name="subdivisions">Subdivisions for the Calculate mode. Don't assign if not using Calculated mode.</param>
/// <returns></returns>
public void Project(Vector3 worldPoint, ref SplineSample result, double from = 0.0, double to = 1.0, EvaluateMode mode = EvaluateMode.Cached, int subdivisions = 4)
{
if (mode == EvaluateMode.Calculate)
{
worldPoint = InverseTransformPoint(worldPoint);
double percent = _spline.Project(InverseTransformPoint(worldPoint), subdivisions, from, to);
_spline.Evaluate(percent, ref result);
TransformSample(ref result);
return;
}
UpdateSampleCollection();
_sampleCollection.Project(worldPoint, _spline.points.Length, ref result, from, to);
}
public SplineSample Project(Vector3 worldPoint, double from = 0.0, double to = 1.0)
{
SplineSample result = new SplineSample();
Project(worldPoint, ref result, from, to);
return result;
}
/// <summary>
/// Same as Spline.CalculateLength but this takes the computer's transform into account when calculating the length.
/// </summary>
/// <param name="from">Calculate from [0-1] default: 0f</param>
/// <param name="to">Calculate to [0-1] default: 1f</param>
/// <param name="resolution">Resolution [0-1] default: 1f</param>
/// <param name="address">Node address of junctions</param>
/// <returns></returns>
public float CalculateLength(double from = 0.0, double to = 1.0)
{
if (!hasSamples) return 0f;
UpdateSampleCollection();
return _sampleCollection.CalculateLength(from, to);
}
private void TransformSample(ref SplineSample result)
{
result.position = _localToWorldMatrix.MultiplyPoint3x4(result.position);
result.forward = _localToWorldRotationMatrix.MultiplyPoint3x4(result.forward);
result.up = _localToWorldRotationMatrix.MultiplyPoint3x4(result.up);
}
public void Rebuild(bool forceUpdateAll = false)
{
if (forceUpdateAll)
{
SetAllDirty();
}
#if UNITY_EDITOR
if (!_editorIsPlaying)
{
if (editorUpdateMode == EditorUpdateMode.Default)
{
RebuildImmediate(true);
}
return;
}
#endif
_queueResample = updateMode != UpdateMode.None;
}
public void RebuildImmediate()
{
RebuildImmediate(true, true);
}
public void RebuildImmediate(bool calculateSamples = true, bool forceUpdateAll = false)
{
if (calculateSamples)
{
_queueResample = true;
if (forceUpdateAll)
{
SetAllDirty();
}
}
else
{
_queueResample = false;
}
RunUpdate(true);
}
private void RebuildUsers(bool immediate = false)
{
for (int i = _subscribers.Length - 1; i >= 0; i--)
{
if (_subscribers[i] != null)
{
if (immediate)
{
_subscribers[i].RebuildImmediate();
}
else
{
_subscribers[i].Rebuild();
}
}
else
{
ArrayUtility.RemoveAt(ref _subscribers, i);
}
}
if (onRebuild != null)
{
onRebuild();
}
_queueRebuild = false;
}
private void SetAllDirty()
{
for (int i = 0; i < _spline.points.Length; i++)
{
_spline.points[i].isDirty = true;
}
}
private void SetDirty(int index)
{
if (sampleMode == SampleMode.Uniform)
{
SetAllDirty();
return;
}
_spline.points[index].isDirty = true;
}
private void CalculateSamples(bool transformSamples = true)
{
_queueResample = false;
_queueRebuild = true;
if (_spline.points.Length == 0)
{
if (_rawSamples.Length != 0)
{
_rawSamples = new SplineSample[0];
if (transformSamples)
{
TransformSamples();
}
}
return;
}
if (_spline.points.Length == 1)
{
if (_rawSamples.Length != 1)
{
_rawSamples = new SplineSample[1];
if (transformSamples)
{
TransformSamples();
}
}
_spline.Evaluate(0.0, ref _rawSamples[0]);
return;
}
if (_sampleMode == SampleMode.Uniform)
{
_spline.EvaluateUniform(ref _rawSamples, ref _originalSamplePercents);
if (transformSamples)
{
TransformSamples();
}
}
else
{
if (_originalSamplePercents.Length > 0)
{
_originalSamplePercents = new double[0];
}
if (_rawSamples.Length != _spline.iterations)
{
_rawSamples = new SplineSample[_spline.iterations];
for (int i = 0; i < _rawSamples.Length; i++)
{
_rawSamples[i] = new SplineSample();
}
}
if (_sampleCollection.samples.Length != _rawSamples.Length)
{
_sampleCollection.samples = new SplineSample[_rawSamples.Length];
}
for (int i = 0; i < _rawSamples.Length; i++)
{
double percent = (double)i / (_rawSamples.Length - 1);
if (IsDirtySample(percent))
{
_spline.Evaluate(percent, ref _rawSamples[i]);
_sampleCollection.samples[i].FastCopy(ref _rawSamples[i]);
if (transformSamples && _space == Space.Local)
{
TransformSample(ref _sampleCollection.samples[i]);
}
}
}
if (_sampleMode == SampleMode.Optimized && _rawSamples.Length > 2)
{
OptimizeSamples(space == Space.Local);
}
else
{
if (_sampleCollection.optimizedIndices.Length > 0)
{
_sampleCollection.optimizedIndices = new int[0];
}
}
}
_sampleCollection.sampleMode = _sampleMode;
hasSamples = _sampleCollection.length > 0;
for (int i = 0; i < _spline.points.Length; i++)
{
_spline.points[i].isDirty = false;
}
}
private void OptimizeSamples(bool transformSamples)
{
if (_sampleCollection.optimizedIndices.Length != _rawSamples.Length)
{
_sampleCollection.optimizedIndices = new int[_rawSamples.Length];
}
Vector3 lastDirection = _rawSamples[0].forward;
List<SplineSample> optimized = new List<SplineSample>();
for (int i = 0; i < _rawSamples.Length; i++)
{
SplineSample sample = _rawSamples[i];
if (transformSamples)
{
TransformSample(ref sample);
}
Vector3 direction = sample.forward;
if (i < _rawSamples.Length - 1)
{
Vector3 pos = _rawSamples[i + 1].position;
if (transformSamples)
{
pos = _localToWorldMatrix.MultiplyPoint3x4(pos);
}
direction = pos - sample.position;
}
float angle = Vector3.Angle(lastDirection, direction);
bool includeSample = angle >= _optimizeAngleThreshold || i == 0 || i == _rawSamples.Length - 1;
if (includeSample)
{
optimized.Add(sample);
lastDirection = direction;
}
_sampleCollection.optimizedIndices[i] = optimized.Count - 1;
}
_sampleCollection.samples = optimized.ToArray();
}
private void TransformSamples()
{
if (_sampleCollection.samples.Length != _rawSamples.Length)
{
_sampleCollection.samples = new SplineSample[_rawSamples.Length];
}
if (_sampleMode == SampleMode.Optimized && _rawSamples.Length > 2)
{
OptimizeSamples(_space == Space.Local);
} else
{
for (int i = 0; i < _rawSamples.Length; i++)
{
_sampleCollection.samples[i].FastCopy(ref _rawSamples[i]);
if (_space == Space.Local)
{
TransformSample(ref _sampleCollection.samples[i]);
}
}
}
}
bool IsDirtySample(double percent)
{
if (_sampleMode == SampleMode.Uniform) return true;
int currentPoint = PercentToPointIndex(percent);
int from = currentPoint - 1;
int to = currentPoint + 2;
if(_spline.type == Spline.Type.Bezier || _spline.type == Spline.Type.Linear)
{
from = currentPoint;
to = currentPoint + 1;
}
int fromClamped = Mathf.Clamp(from, 0, _spline.points.Length - 1);
int toClamped = Mathf.Clamp(to, 0, _spline.points.Length - 1);
for (int i = fromClamped; i <= toClamped; i++)
{
if (_spline.points[i].isDirty)
{
return true;
}
}
if (_spline.isClosed)
{
if(from < 0)
{
for (int i = from + _spline.points.Length; i < _spline.points.Length; i++)
{
if (_spline.points[i].isDirty)
{
return true;
}
}
}
if(to >= _spline.points.Length)
{
for (int i = 0; i <= to - _spline.points.Length; i++)
{
if (_spline.points[i].isDirty)
{
return true;
}
}
}
}
if (currentPoint > 0 && !_spline.points[currentPoint].isDirty)
{
int count = _spline.points.Length - 1;
if (_spline.isClosed)
{
count = _spline.points.Length;
}
double currentPointPercent = (double)currentPoint / count;
if(Mathf.Abs((float)(currentPointPercent - percent)) <= 0.00001f)
{
return _spline.points[currentPoint - 1].isDirty;
}
}
return false;
}
/// <summary>
/// Same as Spline.Break() but it will update all subscribed users
/// </summary>
public void Break()
{
Break(0);
}
/// <summary>
/// Same as Spline.Break(at) but it will update all subscribed users
/// </summary>
/// <param name="at"></param>
public void Break(int at)
{
if (_spline.isClosed)
{
_spline.Break(at);
SetAllDirty();
Rebuild();
}
}
/// <summary>
/// Same as Spline.Close() but it will update all subscribed users
/// </summary>
public void Close()
{
if (!_spline.isClosed)
{
if(_spline.points.Length >= 3)
{
_spline.Close();
SetAllDirty();
Rebuild();
} else
{
Debug.LogError("Spline " + name + " needs at least 3 points before it can be closed. Current points: " + _spline.points.Length);
}
}
}
/// <summary>
/// Same as Spline.HermiteToBezierTangents() but it will update all subscribed users
/// </summary>
public void CatToBezierTangents()
{
_spline.CatToBezierTangents();
SetPoints(_spline.points, Space.Local);
}
/// <summary>
/// Casts a ray along the transformed spline against all scene colliders.
/// </summary>
/// <param name="hit">Hit information</param>
/// <param name="hitPercent">The percent of evaluation where the hit occured</param>
/// <param name="layerMask">Layer mask for the raycast</param>
/// <param name="resolution">Resolution multiplier for precision [0-1] default: 1f</param>
/// <param name="from">Raycast from [0-1] default: 0f</param>
/// <param name="to">Raycast to [0-1] default: 1f</param>
/// <param name="hitTriggers">Should hit triggers? (not supported in 5.1)</param>
/// <param name="address">Node address of junctions</param>
/// <returns></returns>
public bool Raycast(out RaycastHit hit, out double hitPercent, LayerMask layerMask, double resolution = 1.0, double from = 0.0, double to = 1.0 , QueryTriggerInteraction hitTriggers = QueryTriggerInteraction.UseGlobal)
{
resolution = DMath.Clamp01(resolution);
Spline.FormatFromTo(ref from, ref to, false);
double percent = from;
Vector3 fromPos = EvaluatePosition(percent);
hitPercent = 0f;
while (true)
{
double prevPercent = percent;
percent = DMath.Move(percent, to, moveStep / resolution);
Vector3 toPos = EvaluatePosition(percent);
if (Physics.Linecast(fromPos, toPos, out hit, layerMask, hitTriggers))
{
double segmentPercent = (hit.point - fromPos).sqrMagnitude / (toPos - fromPos).sqrMagnitude;
hitPercent = DMath.Lerp(prevPercent, percent, segmentPercent);
return true;
}
fromPos = toPos;
if (percent == to) break;
}
return false;
}
/// <summary>
/// Casts a ray along the transformed spline against all scene colliders and returns all hits. Order is not guaranteed.
/// </summary>
/// <param name="hit">Hit information</param>
/// <param name="hitPercent">The percents of evaluation where each hit occured</param>
/// <param name="layerMask">Layer mask for the raycast</param>
/// <param name="resolution">Resolution multiplier for precision [0-1] default: 1f</param>
/// <param name="from">Raycast from [0-1] default: 0f</param>
/// <param name="to">Raycast to [0-1] default: 1f</param>
/// <param name="hitTriggers">Should hit triggers? (not supported in 5.1)</param>
/// <param name="address">Node address of junctions</param>
/// <returns></returns>
public bool RaycastAll(out RaycastHit[] hits, out double[] hitPercents, LayerMask layerMask, double resolution = 1.0, double from = 0.0, double to = 1.0, QueryTriggerInteraction hitTriggers = QueryTriggerInteraction.UseGlobal)
{
resolution = DMath.Clamp01(resolution);
Spline.FormatFromTo(ref from, ref to, false);
double percent = from;
Vector3 fromPos = EvaluatePosition(percent);
List<RaycastHit> hitList = new List<RaycastHit>();
List<double> percentList = new List<double>();
bool hasHit = false;
while (true)
{
double prevPercent = percent;
percent = DMath.Move(percent, to, moveStep / resolution);
Vector3 toPos = EvaluatePosition(percent);
RaycastHit[] h = Physics.RaycastAll(fromPos, toPos - fromPos, Vector3.Distance(fromPos, toPos), layerMask, hitTriggers);
for (int i = 0; i < h.Length; i++)
{
hasHit = true;
double segmentPercent = (h[i].point - fromPos).sqrMagnitude / (toPos - fromPos).sqrMagnitude;
percentList.Add(DMath.Lerp(prevPercent, percent, segmentPercent));
hitList.Add(h[i]);
}
fromPos = toPos;
if (percent == to) break;
}
hits = hitList.ToArray();
hitPercents = percentList.ToArray();
return hasHit;
}
public TriggerGroup AddTriggerGroup()
{
TriggerGroup newGroup = new TriggerGroup();
ArrayUtility.Add(ref triggerGroups, newGroup);
return newGroup;
}
public SplineTrigger AddTrigger(int triggerGroup, double position, SplineTrigger.Type type)
{
return AddTrigger(triggerGroup, position, type, "API Trigger", Color.white);
}
public SplineTrigger AddTrigger(int triggerGroup, double position, SplineTrigger.Type type, string name, Color color)
{
while (triggerGroups.Length <= triggerGroup)
{
AddTriggerGroup();
}
return triggerGroups[triggerGroup].AddTrigger(position, type, name, color);
}
public void RemoveTrigger(int triggerGroup, int triggerIndex)
{
if(triggerGroups.Length <= triggerGroup || triggerGroup < 0)
{
Debug.LogError("Cannot delete trigger - trigger group " + triggerIndex + " does not exist");
return;
}
triggerGroups[triggerGroup].RemoveTrigger(triggerIndex);
}
public void CheckTriggers(double start, double end, SplineUser user = null)
{
for (int i = 0; i < triggerGroups.Length; i++)
{
triggerGroups[i].Check(start, end);
}
}
public void CheckTriggers(int group, double start, double end)
{
if (group < 0 || group >= triggerGroups.Length)
{
Debug.LogError("Trigger group " + group + " does not exist");
return;
}
triggerGroups[group].Check(start, end);
}
public void ResetTriggers()
{
for (int i = 0; i < triggerGroups.Length; i++) triggerGroups[i].Reset();
}
public void ResetTriggers(int group)
{
if (group < 0 || group >= triggerGroups.Length)
{
Debug.LogError("Trigger group " + group + " does not exist");
return;
}
for (int i = 0; i < triggerGroups[group].triggers.Length; i++)
{
triggerGroups[group].triggers[i].Reset();
}
}
/// <summary>
/// Get the available junctions for the given point
/// </summary>
/// <param name="pointIndex"></param>
/// <returns></returns>
public List<Node.Connection> GetJunctions(int pointIndex)
{
for (int i = 0; i < _nodes.Length; i++)
{
if(_nodes[i].pointIndex == pointIndex) return _nodes[i].GetConnections(this);
}
return new List<Node.Connection>();
}
/// <summary>
/// Get all junctions for all points in the given interval
/// </summary>
/// <param name="start"></param>
/// <param name="direction"></param>
/// <returns></returns>
public Dictionary<int, List<Node.Connection>> GetJunctions(double start = 0.0, double end = 1.0)
{
int index;
double lerp;
UpdateSampleCollection();
_sampleCollection.GetSamplingValues(start, out index, out lerp);
Dictionary<int, List<Node.Connection>> junctions = new Dictionary<int, List<Node.Connection>>();
float startValue = (_spline.points.Length - 1) * (float)start;
float endValue = (_spline.points.Length - 1) * (float)end;
for (int i = 0; i < _nodes.Length; i++)
{
bool add = false;
if (end > start && _nodes[i].pointIndex > startValue && _nodes[i].pointIndex < endValue) add = true;
else if (_nodes[i].pointIndex < startValue && _nodes[i].pointIndex > endValue) add = true;
if (!add && Mathf.Abs(startValue - _nodes[i].pointIndex) <= 0.0001f) add = true;
if (!add && Mathf.Abs(endValue - _nodes[i].pointIndex) <= 0.0001f) add = true;
if (add) junctions.Add(_nodes[i].pointIndex, _nodes[i].GetConnections(this));
}
return junctions;
}
/// <summary>
/// Call this to connect a node to a spline's point
/// </summary>
/// <param name="node"></param>
/// <param name="pointIndex"></param>
public void ConnectNode(Node node, int pointIndex)
{
if (node == null)
{
Debug.LogError("Missing Node");
return;
}
if (pointIndex < 0 || pointIndex >= _spline.points.Length)
{
Debug.Log("Invalid point index " + pointIndex);
return;
}
for (int i = 0; i < _nodes.Length; i++)
{
if (_nodes[i].node == null) continue;
if (_nodes[i].pointIndex == pointIndex || _nodes[i].node == node)
{
Node.Connection[] connections = _nodes[i].node.GetConnections();
for (int j = 0; j < connections.Length; j++)
{
if (connections[j].spline == this)
{
Debug.LogError("Node " + node.name + " is already connected to spline " + name + " at point " + _nodes[i].pointIndex);
return;
}
}
AddNodeLink(node, pointIndex);
Debug.Log("Node link already exists");
return;
}
}
node.AddConnection(this, pointIndex);
AddNodeLink(node, pointIndex);
}
public void DisconnectNode(int pointIndex)
{
for (int i = 0; i < _nodes.Length; i++)
{
if (_nodes[i].pointIndex == pointIndex)
{
_nodes[i].node.RemoveConnection(this, pointIndex);
ArrayUtility.RemoveAt(ref _nodes, i);
return;
}
}
}
private void AddNodeLink(Node node, int pointIndex)
{
NodeLink newLink = new NodeLink();
newLink.node = node;
newLink.pointIndex = pointIndex;
ArrayUtility.Add(ref _nodes, newLink);
UpdateConnectedNodes();
}
public Dictionary<int, Node> GetNodes(double start = 0.0, double end = 1.0)
{
int index;
double lerp;
UpdateSampleCollection();
_sampleCollection.GetSamplingValues(start, out index, out lerp);
Dictionary<int, Node> nodeList = new Dictionary<int, Node>();
float startValue = (_spline.points.Length - 1) * (float)start;
float endValue = (_spline.points.Length - 1) * (float)end;
for (int i = 0; i < _nodes.Length; i++)
{
bool add = false;
if (end > start && _nodes[i].pointIndex > startValue && _nodes[i].pointIndex < endValue) add = true;
else if (_nodes[i].pointIndex < startValue && _nodes[i].pointIndex > endValue) add = true;
if (!add && Mathf.Abs(startValue - _nodes[i].pointIndex) <= 0.0001f) add = true;
if (!add && Mathf.Abs(endValue - _nodes[i].pointIndex) <= 0.0001f) add = true;
if (add) nodeList.Add(_nodes[i].pointIndex, _nodes[i].node);
}
return nodeList;
}
public Node GetNode(int pointIndex)
{
if (pointIndex < 0 || pointIndex >= _spline.points.Length) return null;
for (int i = 0; i < _nodes.Length; i++)
{
if (_nodes[i].pointIndex == pointIndex) return _nodes[i].node;
}
return null;
}
public void TransferNode(int pointIndex, int newPointIndex)
{
if(newPointIndex < 0 || newPointIndex >= _spline.points.Length)
{
Debug.LogError("Invalid new point index " + newPointIndex);
return;
}
if (GetNode(newPointIndex) != null)
{
Debug.LogError("Cannot move node to point " + newPointIndex + ". Point already connected to a node");
return;
}
Node node = GetNode(pointIndex);
if(node == null)
{
Debug.LogError("No node connected to point " + pointIndex);
return;
}
DisconnectNode(pointIndex);
SplineSample sample = Evaluate(newPointIndex);
node.transform.position = sample.position;
node.transform.rotation = sample.rotation;
ConnectNode(node, newPointIndex);
}
public void ShiftNodes(int startIndex, int endIndex, int shift)
{
int from = endIndex;
int to = startIndex;
if(startIndex > endIndex)
{
from = startIndex;
to = endIndex;
}
for (int i = from; i >= to; i--)
{
Node node = GetNode(i);
if (node != null)
{
TransferNode(i, i + shift);
}
}
}
/// <summary>
/// Gets all connected computers along with the connected indices and connection indices
/// </summary>
/// <param name="computers">A list of the connected computers</param>
/// <param name="connectionIndices">The point indices of this computer where the other computers are connected</param>
/// <param name="connectedIndices">The point indices of the other computers where they are connected</param>
/// <param name="percent"></param>
/// <param name="direction"></param>
/// <param name="includeEqual">Should point indices that are placed exactly at the percent be included?</param>
public void GetConnectedComputers(List<SplineComputer> computers, List<int> connectionIndices, List<int> connectedIndices, double percent, Spline.Direction direction, bool includeEqual)
{
if (computers == null) computers = new List<SplineComputer>();
if (connectionIndices == null) connectionIndices = new List<int>();
if (connectedIndices == null) connectionIndices = new List<int>();
computers.Clear();
connectionIndices.Clear();
connectedIndices.Clear();
int pointValue = Mathf.FloorToInt((_spline.points.Length - 1) * (float)percent);
for (int i = 0; i < _nodes.Length; i++)
{
bool condition = false;
if (includeEqual)
{
if (direction == Spline.Direction.Forward) condition = _nodes[i].pointIndex >= pointValue;
else condition = _nodes[i].pointIndex <= pointValue;
} else
{
}
if (condition)
{
Node.Connection[] connections = _nodes[i].node.GetConnections();
for (int j = 0; j < connections.Length; j++)
{
if (connections[j].spline != this) {
computers.Add(connections[j].spline);
connectionIndices.Add(_nodes[i].pointIndex);
connectedIndices.Add(connections[j].pointIndex);
}
}
}
}
}
/// <summary>
/// Returns a list of all connected computers. This includes the base computer too.
/// </summary>
/// <returns></returns>
public List<SplineComputer> GetConnectedComputers()
{
List<SplineComputer> computers = new List<SplineComputer>();
computers.Add(this);
if (_nodes.Length == 0) return computers;
GetConnectedComputers(ref computers);
return computers;
}
public void GetSamplingValues(double percent, out int index, out double lerp)
{
UpdateSampleCollection();
_sampleCollection.GetSamplingValues(percent, out index, out lerp);
}
private void GetConnectedComputers(ref List<SplineComputer> computers)
{
SplineComputer comp = computers[computers.Count - 1];
if (comp == null) return;
for (int i = 0; i < comp._nodes.Length; i++)
{
if (comp._nodes[i].node == null) continue;
Node.Connection[] connections = comp._nodes[i].node.GetConnections();
for (int n = 0; n < connections.Length; n++)
{
bool found = false;
if (connections[n].spline == this) continue;
for (int x = 0; x < computers.Count; x++)
{
if (computers[x] == connections[n].spline)
{
found = true;
break;
}
}
if (!found)
{
computers.Add(connections[n].spline);
GetConnectedComputers(ref computers);
}
}
}
}
private void RemoveNodeLinkAt(int index)
{
//Then remove the node link
NodeLink[] newLinks = new NodeLink[_nodes.Length - 1];
for (int i = 0; i < _nodes.Length; i++)
{
if (i == index) continue;
else if (i < index) newLinks[i] = _nodes[i];
else newLinks[i - 1] = _nodes[i];
}
_nodes = newLinks;
}
//This "magically" updates the Node's position and all other points, connected to it when a point, linked to a Node is changed.
private void SetNodeForPoint(int index, SplinePoint worldPoint)
{
for (int i = 0; i < _nodes.Length; i++)
{
if (_nodes[i].pointIndex == index)
{
_nodes[i].node.UpdatePoint(this, _nodes[i].pointIndex, worldPoint);
break;
}
}
}
private void UpdateConnectedNodes(SplinePoint[] worldPoints)
{
for (int i = 0; i < _nodes.Length; i++)
{
if (_nodes[i].node == null)
{
RemoveNodeLinkAt(i);
i--;
Rebuild();
continue;
}
bool found = false;
foreach(Node.Connection connection in _nodes[i].node.GetConnections())
{
if(connection.spline == this)
{
found = true;
break;
}
}
if (!found)
{
RemoveNodeLinkAt(i);
i--;
Rebuild();
continue;
}
_nodes[i].node.UpdatePoint(this, _nodes[i].pointIndex, worldPoints[_nodes[i].pointIndex]);
_nodes[i].node.UpdateConnectedComputers(this);
}
}
private void UpdateConnectedNodes()
{
for (int i = 0; i < _nodes.Length; i++)
{
if (_nodes[i] == null || _nodes[i].node == null)
{
RemoveNodeLinkAt(i);
Rebuild();
i--;
continue;
}
bool found = false;
Node.Connection[] connections = _nodes[i].node.GetConnections();
for (int j = 0; j < connections.Length; j++)
{
if(connections[j].spline == this && connections[j].pointIndex == _nodes[i].pointIndex)
{
found = true;
break;
}
}
if (found)
{
_nodes[i].node.UpdatePoint(this, _nodes[i].pointIndex, GetPoint(_nodes[i].pointIndex));
} else
{
RemoveNodeLinkAt(i);
Rebuild();
i--;
continue;
}
}
}
public Vector3 TransformPoint(Vector3 point)
{
#if UNITY_EDITOR
if (!_editorIsPlaying) return transform.TransformPoint(point);
#endif
return _localToWorldMatrix.MultiplyPoint3x4(point);
}
public Vector3 InverseTransformPoint(Vector3 point)
{
#if UNITY_EDITOR
if (!_editorIsPlaying) return transform.InverseTransformPoint(point);
#endif
return _worldToLocalMatrix.MultiplyPoint3x4(point);
}
public Vector3 TransformDirection(Vector3 direction)
{
#if UNITY_EDITOR
if (!_editorIsPlaying) return transform.TransformDirection(direction);
#endif
return _localToWorldMatrix.MultiplyVector(direction);
}
public Vector3 InverseTransformDirection(Vector3 direction)
{
#if UNITY_EDITOR
if (!_editorIsPlaying) return transform.InverseTransformDirection(direction);
#endif
return _worldToLocalMatrix.MultiplyVector(direction);
}
#if UNITY_EDITOR
public void EditorSetPointDirty(int index)
{
SetDirty(index);
}
public void EditorSetAllPointsDirty()
{
SetAllDirty();
}
#endif
[System.Serializable]
internal class NodeLink
{
[SerializeField]
internal Node node = null;
[SerializeField]
internal int pointIndex = 0;
internal List<Node.Connection> GetConnections(SplineComputer exclude)
{
Node.Connection[] connections = node.GetConnections();
List<Node.Connection> connectionList = new List<Node.Connection>();
for (int i = 0; i < connections.Length; i++)
{
if (connections[i].spline == exclude) continue;
connectionList.Add(connections[i]);
}
return connectionList;
}
}
}
}