using R3; using UnityEngine; using System; using KitsuneCafe.System; using System.Collections.Generic; namespace KitsuneCafe.Extension { public static class ObservableExtensions { public static Observable Compose(this Observable source, Func, Observable> transform) { return transform(source); } private struct SmoothDampState { public float CurrentValue; public float CurrentVelocityRef; } public static Observable EveryUpdateDelta(this TimeProvider provider) { if (provider == null) { provider = UnityTimeProvider.Update; } if (provider.TryGetFrameProvider(out var frameProvider)) { return Observable.EveryUpdate(frameProvider) .Select(_ => provider.GetDeltaTime(Time.deltaTime)); } else { return Observable.Empty(); } } /// /// Smoothly dampens a value from its current state towards a target value over time, /// driven by a FrameProvider. /// /// The observable stream providing the *target* values for damping. /// A function that provides the initial value for the damping process when a new target is received (e.g., current value from Animator). /// The approximate time it takes for the value to reach the target. /// The maximum speed the value can change per second. /// The FrameProvider to use for update ticks (e.g., UnityFrameProvider.FixedUpdate). /// An observable stream of the smoothed values. public static Observable SmoothDamp( this Observable source, Func valueGetter, float smoothTime, float maxSpeed, TimeProvider provider) { return source .DistinctUntilChanged(FEqualityComparer.Create(MathExtension.IsApproxEqual)) .Select(target => { float initialCurrentValue = valueGetter(); return Observable.EveryUpdate(provider.GetFrameProvider()) .Scan(new SmoothDampState { CurrentValue = initialCurrentValue, CurrentVelocityRef = 0f }, (state, _) => { float currentVelocity = state.CurrentVelocityRef; float nextValue = Mathf.SmoothDamp( state.CurrentValue, target, ref currentVelocity, smoothTime, maxSpeed, provider.GetDeltaTime() ); return new SmoothDampState { CurrentValue = nextValue, CurrentVelocityRef = currentVelocity }; }) .Select(state => state.CurrentValue) .TakeWhile(val => Mathf.Abs(val - target) > float.Epsilon) .Concat(Observable.Return(target)); }) .Switch(); } public static Observable SmoothDamp( this Observable source, Func valueGetter, float smoothTime) { return SmoothDamp(source, valueGetter, smoothTime, Mathf.Infinity, UnityTimeProvider.Update); } public static Observable SmoothDamp( this Observable source, Func valueGetter, float smoothTime, float maxSpeed) { return SmoothDamp(source, valueGetter, smoothTime, maxSpeed, UnityTimeProvider.Update); } public static Observable SmoothDamp( this Observable source, Func valueGetter, float smoothTime, TimeProvider provider) { return SmoothDamp(source, valueGetter, smoothTime, Mathf.Infinity, provider); } public static float SqrDistance(this Vector3 vector, Vector3 other) { return (vector - other).sqrMagnitude; } /// /// Returns the element from a sequence that has the minimum value for a specified key. /// If the source sequence is empty, returns default(TSource). /// /// The type of the elements in the source sequence. /// The type of the key to compare elements by. /// The sequence to find the minimum element from. /// A function to extract the key from an element. /// The element that has the minimum key value, or default(TSource) if the source is empty. public static TSource MinBy(this IEnumerable source, Func keySelector) where TKey : IComparable { if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); using (var enumerator = source.GetEnumerator()) { if (!enumerator.MoveNext()) { return default; } TSource minElement = enumerator.Current; TKey minKey = keySelector(minElement); while (enumerator.MoveNext()) { TSource currentElement = enumerator.Current; TKey currentKey = keySelector(currentElement); if (currentKey.CompareTo(minKey) < 0) { minKey = currentKey; minElement = currentElement; } } return minElement; } } } }