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

895 lines
26 KiB
C#
Raw Permalink Normal View History

2023-07-24 16:38:13 +03:00
using UnityEngine;
namespace Dreamteck.Splines {
[ExecuteInEditMode]
public class SplineUser : MonoBehaviour, ISerializationCallbackReceiver
{
public enum UpdateMethod { Update, FixedUpdate, LateUpdate }
[HideInInspector]
public UpdateMethod updateMethod = UpdateMethod.Update;
public SplineComputer spline
{
get {
return _spline;
}
set
{
if (value != _spline)
{
if (_spline != null)
{
_spline.Unsubscribe(this);
}
_spline = value;
if (_spline != null)
{
_spline.Subscribe(this);
Rebuild();
}
OnSplineChanged();
}
}
}
public double clipFrom
{
get
{
return _clipFrom;
}
set
{
if (value != _clipFrom)
{
animClipFrom = (float)_clipFrom;
_clipFrom = DMath.Clamp01(value);
if (_clipFrom > _clipTo)
{
if (!_spline.isClosed) _clipTo = _clipFrom;
}
getSamples = true;
Rebuild();
}
}
}
public double clipTo
{
get
{
return _clipTo;
}
set
{
if (value != _clipTo)
{
animClipTo = (float)_clipTo;
_clipTo = DMath.Clamp01(value);
if (_clipTo < _clipFrom)
{
if (!_spline.isClosed) _clipFrom = _clipTo;
}
getSamples = true;
Rebuild();
}
}
}
public bool autoUpdate
{
get
{
return _autoUpdate;
}
set
{
if (value != _autoUpdate)
{
_autoUpdate = value;
if (value) Rebuild();
}
}
}
public bool loopSamples
{
get
{
return _loopSamples;
}
set
{
if (value != _loopSamples)
{
_loopSamples = value;
if(!_loopSamples && _clipTo < _clipFrom)
{
double temp = _clipTo;
_clipTo = _clipFrom;
_clipFrom = temp;
}
Rebuild();
}
}
}
//The percent of the spline that we're traversing
public double span
{
get
{
if (samplesAreLooped) return (1.0 - _clipFrom) + _clipTo;
return _clipTo - _clipFrom;
}
}
public bool samplesAreLooped
{
get
{
return _loopSamples && _clipFrom >= _clipTo;
}
}
public RotationModifier rotationModifier
{
get
{
return _rotationModifier;
}
}
public OffsetModifier offsetModifier
{
get
{
return _offsetModifier;
}
}
public ColorModifier colorModifier
{
get
{
return _colorModifier;
}
}
public SizeModifier sizeModifier
{
get
{
return _sizeModifier;
}
}
//Serialized values
[SerializeField]
[HideInInspector]
private SplineComputer _spline;
[SerializeField]
[HideInInspector]
private bool _autoUpdate = true;
[SerializeField]
[HideInInspector]
protected RotationModifier _rotationModifier = new RotationModifier();
[SerializeField]
[HideInInspector]
protected OffsetModifier _offsetModifier = new OffsetModifier();
[SerializeField]
[HideInInspector]
protected ColorModifier _colorModifier = new ColorModifier();
[SerializeField]
[HideInInspector]
protected SizeModifier _sizeModifier = new SizeModifier();
[SerializeField]
[HideInInspector]
private SplineSample _clipFromSample = new SplineSample(), _clipToSample = new SplineSample();
[SerializeField]
[HideInInspector]
private bool _loopSamples = false;
[SerializeField]
[HideInInspector]
private double _clipFrom = 0.0;
[SerializeField]
[HideInInspector]
private double _clipTo = 1.0;
//float values used for making animations
[SerializeField]
[HideInInspector]
private float animClipFrom = 0f;
[SerializeField]
[HideInInspector]
private float animClipTo = 1f;
private SampleCollection _sampleCollection = new SampleCollection();
private bool rebuild = false, getSamples = false, postBuild = false;
private Transform _trs = null;
private bool _hasTransform = false;
private SplineSample _workSample = new SplineSample();
#if UNITY_EDITOR
private bool _isPlaying = false;
protected bool isPlaying => _isPlaying;
#endif
protected Transform trs
{
get { return _trs; }
}
protected bool hasTransform
{
get { return _hasTransform; }
}
public int sampleCount
{
get { return _sampleCount; }
}
private int _sampleCount = 0, _startSampleIndex = 0;
/// <summary>
/// Use this to work with the Evaluate and Project methods
/// </summary>
protected SplineSample evalResult = new SplineSample();
//Threading values
[HideInInspector]
public volatile bool multithreaded = false;
[HideInInspector]
public bool buildOnAwake = true;
[HideInInspector]
public bool buildOnEnable = false;
public event EmptySplineHandler onPostBuild;
#if UNITY_EDITOR
public virtual void EditorAwake()
{
}
#endif
protected virtual void Awake() {
#if UNITY_EDITOR
_isPlaying = Application.isPlaying;
if (!_isPlaying)
{
if (spline != null)
{
if (!_spline.IsSubscribed(this))
{
_spline.Subscribe(this);
UnityEditor.EditorUtility.SetDirty(spline);
}
}
}
#endif
CacheTransform();
if (buildOnAwake && Application.isPlaying)
{
RebuildImmediate();
} else
{
GetSamples();
}
#if UNITY_EDITOR
if (!Application.isPlaying)
{
RebuildImmediate();
}
#endif
}
protected void CacheTransform()
{
_trs = transform;
_hasTransform = true;
}
protected virtual void Reset()
{
#if UNITY_EDITOR
spline = GetComponent<SplineComputer>();
Awake();
#endif
}
protected virtual void OnEnable()
{
#if UNITY_EDITOR
if (!_isPlaying || buildOnEnable)
{
RebuildImmediate();
}
#else
if (buildOnEnable){
RebuildImmediate();
}
#endif
}
protected virtual void OnDisable()
{
}
protected virtual void OnDestroy()
{
#if UNITY_EDITOR
if (!_isPlaying && spline != null)
{
_spline.Unsubscribe(this); //Unsubscribe if DestroyImmediate is called
}
#endif
}
protected virtual void OnDidApplyAnimationProperties()
{
bool clip = false;
if (_clipFrom != animClipFrom || _clipTo != animClipTo) clip = true;
_clipFrom = animClipFrom;
_clipTo = animClipTo;
Rebuild();
if (clip) GetSamples();
}
/// <summary>
/// Gets the sample at the given index without modifications
/// </summary>
/// <param name="index">Sample index</param>
/// <returns></returns>
public void GetSampleRaw(int index, ref SplineSample sample)
{
if (index == 0)
{
sample.FastCopy(ref _clipFromSample);
return;
}
if (index == _sampleCount - 1)
{
sample.FastCopy(ref _clipToSample);
return;
}
ClampLoopSampleIndex(ref index);
sample.FastCopy(ref _sampleCollection.samples[index]);
}
public double GetSamplePercent(int index)
{
if (index == 0)
{
return _clipFromSample.percent;
}
if (index == _sampleCount - 1)
{
return _clipToSample.percent;
}
ClampLoopSampleIndex(ref index);
return _sampleCollection.samples[index].percent;
}
private void ClampLoopSampleIndex(ref int index)
{
if (index >= _sampleCount)
{
index = _sampleCount - 1;
}
if (samplesAreLooped)
{
int start;
double lerp;
_sampleCollection.GetSamplingValues(clipFrom, out start, out lerp);
index = start + index;
if (index >= _sampleCollection.length)
{
index -= _sampleCollection.length;
}
}
else
{
index = _startSampleIndex + index;
}
}
/// <summary>
/// Returns the sample at the given index with modifiers applied
/// </summary>
/// <param name="index">Sample index</param>
/// <param name="target">Sample to write to</param>
public void GetSample(int index, ref SplineSample target)
{
GetSampleRaw(index, ref _workSample);
ModifySample(ref _workSample, ref target);
}
/// <summary>
/// Returns the sample at the given index with modifiers applied and
/// applies compensation to the size parameter based on the angle between the samples
/// </summary>
public void GetSampleWithAngleCompensation(int index, ref SplineSample target)
{
GetSampleRaw(index, ref target);
ModifySample(ref target, ref target);
if(index > 0 && index < sampleCount - 1)
{
GetSampleRaw(index - 1, ref _workSample);
ModifySample(ref _workSample, ref _workSample);
Vector3 prev = target.position - _workSample.position;
GetSampleRaw(index + 1, ref _workSample);
ModifySample(ref _workSample, ref _workSample);
Vector3 next = _workSample.position - target.position;
target.size *= 1 / Mathf.Sqrt(Vector3.Dot(prev.normalized, next.normalized) * 0.5f + 0.5f);
}
}
/// <summary>
/// Rebuild the SplineUser. This will cause Build and Build_MT to be called.
/// </summary>
/// <param name="sampleComputer">Should the SplineUser sample the SplineComputer</param>
public virtual void Rebuild()
{
#if UNITY_EDITOR
if (!_hasTransform)
{
CacheTransform();
}
//If it's the editor and it's not playing, then rebuild immediate
if (_isPlaying)
{
if (!autoUpdate) return;
rebuild = getSamples = true;
}
else
{
RebuildImmediate();
}
#else
if (!autoUpdate) return;
rebuild = getSamples = true;
#endif
}
/// <summary>
/// Rebuild the SplineUser immediate. This method will call sample samples and call Build as soon as it's called even if the component is disabled.
/// </summary>
/// <param name="sampleComputer">Should the SplineUser sample the SplineComputer</param>
public virtual void RebuildImmediate()
{
#if UNITY_EDITOR
if (!_hasTransform)
{
CacheTransform();
}
#endif
try
{
GetSamples();
Build();
PostBuild();
}
catch (System.Exception ex)
{
Debug.LogError(ex.Message);
}
rebuild = false;
getSamples = false;
}
private void Update()
{
if (updateMethod == UpdateMethod.Update)
{
Run();
RunUpdate();
LateRun();
}
}
private void LateUpdate()
{
if (updateMethod == UpdateMethod.LateUpdate)
{
Run();
RunUpdate();
LateRun();
}
#if UNITY_EDITOR
if(!_isPlaying && updateMethod == UpdateMethod.FixedUpdate)
{
Run();
RunUpdate();
LateRun();
}
#endif
}
private void FixedUpdate()
{
if (updateMethod == UpdateMethod.FixedUpdate)
{
Run();
RunUpdate();
LateRun();
}
}
//Update logic for handling threads and rebuilding
private void RunUpdate()
{
#if UNITY_EDITOR
if (!_isPlaying) return;
#endif
//Handle rebuilding
if (rebuild)
{
if (multithreaded)
{
if (getSamples) SplineThreading.Run(ResampleAndBuildThreaded);
else SplineThreading.Run(BuildThreaded);
}
else
{
if (getSamples || _spline.sampleMode == SplineComputer.SampleMode.Optimized) GetSamples();
Build();
postBuild = true;
}
rebuild = false;
}
if (postBuild)
{
PostBuild();
EmptySplineHandler postBuildHandler = onPostBuild;
if(postBuildHandler != null)
{
postBuildHandler();
}
postBuild = false;
}
}
void BuildThreaded()
{
while (postBuild)
{
//Wait if the main thread is still running post build operations
}
Build();
postBuild = true;
}
private void ResampleAndBuildThreaded()
{
while (postBuild)
{
//Wait if the main thread is still running post build operations
}
GetSamples();
Build();
postBuild = true;
}
/// Code to run every Update/FixedUpdate/LateUpdate before any building has taken place
protected virtual void Run()
{
}
/// Code to run every Update/FixedUpdate/LateUpdate after any rabuilding has taken place
protected virtual void LateRun()
{
}
//Used for calculations. Called on the main or the worker thread.
protected virtual void Build()
{
}
//Called on the Main thread only - used for applying the results from Build
protected virtual void PostBuild()
{
}
protected virtual void OnSplineChanged()
{
}
/// <summary>
/// Applies the SplineUser modifiers to the provided sample
/// </summary>
/// <param name="source">Original sample</param>
/// <param name="destination">Destination sample</param>
public void ModifySample(ref SplineSample source, ref SplineSample destination)
{
destination = source;
ModifySample(ref destination);
}
/// <summary>
/// Applies the SplineUser modifiers to the provided sample
/// </summary>
/// <param name="sample"></param>
public void ModifySample(ref SplineSample sample)
{
ApplyModifier(_offsetModifier, ref sample);
ApplyModifier(_rotationModifier, ref sample);
ApplyModifier(_colorModifier, ref sample);
ApplyModifier(_sizeModifier, ref sample);
}
private void ApplyModifier(SplineSampleModifier modifier, ref SplineSample sample)
{
if (modifier.useClippedPercent)
{
ClipPercent(ref sample.percent);
}
modifier.Apply(ref sample);
if (modifier.useClippedPercent)
{
UnclipPercent(ref sample.percent);
}
}
/// <summary>
/// Sets the clip range of the SplineUser. Same as setting clipFrom and clipTo
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
public void SetClipRange(double from, double to)
{
if (!_spline.isClosed && to < from) to = from;
_clipFrom = DMath.Clamp01(from);
_clipTo = DMath.Clamp01(to);
GetSamples();
Rebuild();
}
/// <summary>
/// Gets the clipped samples defined by clipFrom and clipTo
/// </summary>
private void GetSamples()
{
getSamples = false;
if (spline == null)
{
_sampleCollection.samples = new SplineSample[0];
_sampleCount = 0;
return;
}
_spline.GetSamples(_sampleCollection);
if(_clipFrom != 0.0)
{
_sampleCollection.Evaluate(clipFrom, ref _clipFromSample);
} else
{
_clipFromSample = _sampleCollection.samples[0];
}
if(_clipTo != 1.0)
{
_sampleCollection.Evaluate(_clipTo, ref _clipToSample);
} else
{
_clipToSample = _sampleCollection.samples[_sampleCollection.length - 1];
}
int start, end;
_sampleCount = _sampleCollection.GetClippedSampleCount(_clipFrom, _clipTo, out start, out end);
double lerp;
_sampleCollection.GetSamplingValues(_clipFrom, out _startSampleIndex, out lerp);
}
/// <summary>
/// Takes a regular 0-1 percent mapped to the start and end of the spline and maps it to the clipFrom and clipTo valies. Useful for working with clipped samples
/// </summary>
/// <param name="percent"></param>
/// <returns></returns>
public double ClipPercent(double percent)
{
ClipPercent(ref percent);
return percent;
}
/// <summary>
/// Takes a regular 0-1 percent mapped to the start and end of the spline and maps it to the clipFrom and clipTo valies. Useful for working with clipped samples
/// </summary>
/// <param name="percent"></param>
/// <returns></returns>
public void ClipPercent(ref double percent)
{
if (_sampleCollection.length == 0)
{
percent = 0.0;
return;
}
if (samplesAreLooped)
{
if (percent >= clipFrom && percent <= 1.0) { percent = DMath.InverseLerp(clipFrom, clipFrom + span, percent); }//If in the range clipFrom - 1.0
else if (percent <= clipTo) { percent = DMath.InverseLerp(clipTo - span, clipTo, percent); } //if in the range 0.0 - clipTo
else
{
//Find the nearest clip start
if (DMath.InverseLerp(clipTo, clipFrom, percent) < 0.5) percent = 1.0;
else percent = 0.0;
}
}
else percent = DMath.InverseLerp(clipFrom, clipTo, percent);
}
public double UnclipPercent(double percent)
{
UnclipPercent(ref percent);
return percent;
}
public void UnclipPercent(ref double percent)
{
if (percent == 0.0)
{
percent = clipFrom;
return;
}
else if (percent == 1.0)
{
percent = clipTo;
return;
}
if (samplesAreLooped)
{
double fromLength = (1.0 - clipFrom) / span;
if (fromLength == 0.0)
{
percent = 0.0;
return;
}
if (percent < fromLength) percent = DMath.Lerp(clipFrom, 1.0, percent / fromLength);
else if (clipTo == 0.0)
{
percent = 0.0;
return;
}
else percent = DMath.Lerp(0.0, clipTo, (percent - fromLength) / (clipTo / span));
}
else percent = DMath.Lerp(clipFrom, clipTo, percent);
percent = DMath.Clamp01(percent);
}
private int GetSampleIndex(double percent)
{
int index;
double lerp;
_sampleCollection.GetSamplingValues(UnclipPercent(percent), out index, out lerp);
return index;
}
public Vector3 EvaluatePosition(double percent)
{
return _sampleCollection.EvaluatePosition(UnclipPercent(percent));
}
public void Evaluate(double percent, ref SplineSample result)
{
_sampleCollection.Evaluate(UnclipPercent(percent), ref result);
result.percent = DMath.Clamp01(percent);
}
public SplineSample Evaluate(double percent)
{
SplineSample result = new SplineSample();
Evaluate(percent, ref result);
result.percent = DMath.Clamp01(percent);
return result;
}
public void Evaluate(ref SplineSample[] results, double from = 0.0, double to = 1.0)
{
_sampleCollection.Evaluate(ref results, UnclipPercent(from), UnclipPercent(to));
for (int i = 0; i < results.Length; i++)
{
ClipPercent(ref results[i].percent);
}
}
public void EvaluatePositions(ref Vector3[] positions, double from = 0.0, double to = 1.0)
{
_sampleCollection.EvaluatePositions(ref positions, UnclipPercent(from), UnclipPercent(to));
}
public double Travel(double start, float distance, Spline.Direction direction, out float moved)
{
moved = 0f;
if (direction == Spline.Direction.Forward && start >= 1.0)
{
return 1.0;
}
else if (direction == Spline.Direction.Backward && start <= 0.0)
{
return 0.0;
}
if (distance == 0f)
{
return DMath.Clamp01(start);
}
double result = _sampleCollection.Travel(UnclipPercent(start), distance, direction, out moved, clipFrom, clipTo);
return ClipPercent(result);
}
public double Travel(double start, float distance, Spline.Direction direction = Spline.Direction.Forward)
{
float moved;
return Travel(start, distance, direction, out moved);
}
public double TravelWithOffset(double start, float distance, Spline.Direction direction, Vector3 offset, out float moved)
{
moved = 0f;
if (direction == Spline.Direction.Forward && start >= 1.0)
{
return 1.0;
}
else if (direction == Spline.Direction.Backward && start <= 0.0)
{
return 0.0;
}
if (distance == 0f)
{
return DMath.Clamp01(start);
}
double result = _sampleCollection.TravelWithOffset(UnclipPercent(start), distance, direction, offset, out moved, clipFrom, clipTo);
return ClipPercent(result);
}
public virtual void Project(Vector3 position, ref SplineSample result, double from = 0.0, double to = 1.0)
{
if (_spline == null) return;
_sampleCollection.Project(position, _spline.pointCount, ref result, UnclipPercent(from), UnclipPercent(to));
ClipPercent(ref result.percent);
}
public float CalculateLength(double from = 0.0, double to = 1.0)
{
return _sampleCollection.CalculateLength(UnclipPercent(from), UnclipPercent(to));
}
public float CalculateLengthWithOffset(Vector3 offset, double from = 0.0, double to = 1.0)
{
return _sampleCollection.CalculateLengthWithOffset(offset, UnclipPercent(from), UnclipPercent(to));
}
public virtual void OnBeforeSerialize()
{
}
public virtual void OnAfterDeserialize()
{
}
/// <summary>
/// Returns the offset transformed by the sample
/// </summary>
/// <param name="sample">Source sample</param>
/// <param name="localOffset">Local offset to apply</param>
/// <returns></returns>
protected static Vector3 TransformOffset(SplineSample sample, Vector3 localOffset)
{
return (sample.right * localOffset.x + sample.up * localOffset.y + sample.forward * localOffset.z) * sample.size;
}
}
}