// River Modeler
// Staggart Creations (http://staggart.xyz)
// Copyright protected under Unity Asset Store EULA
// Copying or referencing source code for the production of new asset store content is strictly prohibited.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
#if MATHEMATICS
using Unity.Mathematics;
#endif
#if SPLINES
using UnityEngine.Splines;
#endif
namespace sc.modeling.river.runtime
{
public partial class RiverModeler
{
#if SPLINES
public SplineContainer splineContainer;
public enum SplineChangeReaction
{
During,
WhenDone,
}
[Tooltip("Determines when a change to the spline should be detected")]
public SplineChangeReaction splineChangeMode = SplineChangeReaction.During;
#if MATHEMATICS
///
/// X-component defines the width of the river. Y-component scales the displacement. Sample data using length units.
///
public List> ScaleData = new List>();
///
/// Weights for transparency data stored in vertex colors. Sample data using length units.
///
public List> TransparencyData = new List>();
/// Weights for foam strength stored in vertex colors. Normalized value where 0=subtracting 0.5=none 1.0=adding. Sample data using length units.
public List> FoamData = new List>();
#endif
private partial void SubscribeSplineCallbacks()
{
#if SPLINES && MATHEMATICS
SplineContainer.SplineAdded += OnSplineAdded;
SplineContainer.SplineRemoved += OnSplineRemoved;
Spline.Changed += OnSplineChanged;
#endif
}
private partial void UnsubscribeSplineCallbacks()
{
#if SPLINES && MATHEMATICS
SplineContainer.SplineAdded -= OnSplineAdded;
SplineContainer.SplineRemoved -= OnSplineRemoved;
Spline.Changed -= OnSplineChanged;
#endif
}
#endif
#if SPLINES && MATHEMATICS
private int lastEditedSplineIndex = -1;
private void OnSplineChanged(Spline spline, int index, SplineModification modificationType)
{
if (!splineContainer) return;
if (rebuildTriggers.HasFlag(RebuildTriggers.OnSplineChanged) == false) return;
//Spline belongs to the assigned container?
var splineIndex = Array.IndexOf(splineContainer.Splines.ToArray(), spline);
if (splineIndex < 0)
return;
lastEditedSplineIndex = splineIndex;
if (splineChangeMode == SplineChangeReaction.WhenDone)
{
lastChangeTime = Time.realtimeSinceStartup;
if (Application.isPlaying)
{
//Coroutines only work in play mode and builds
//Cancel any existing debounce coroutine
if (debounceCoroutine != null) StopCoroutine(debounceCoroutine);
debounceCoroutine = StartCoroutine(DebounceCoroutine());
}
else
{
if (!isTrackingChanges)
{
isTrackingChanges = true;
#if UNITY_EDITOR
UnityEditor.EditorApplication.update += EditorUpdate;
#endif
}
}
}
else if (splineChangeMode == SplineChangeReaction.During)
{
ExecuteAfterSplineChanges();
}
}
public float debounceTime = 0.1f;
private float lastChangeTime = -1f;
private bool isTrackingChanges = false;
private void EditorUpdate()
{
if (isTrackingChanges && Time.realtimeSinceStartup - lastChangeTime >= debounceTime)
{
ExecuteAfterSplineChanges();
isTrackingChanges = false;
#if UNITY_EDITOR
UnityEditor.EditorApplication.update -= EditorUpdate;
#endif
}
}
private Coroutine debounceCoroutine;
private IEnumerator DebounceCoroutine()
{
yield return new WaitForSeconds(debounceTime);
ExecuteAfterSplineChanges();
}
private void ExecuteAfterSplineChanges()
{
if(lastEditedSplineIndex < 0) return;
Rebuild(ChangeFlags.Spline);
}
void OnSplineAdded(SplineContainer container, int index)
{
if (rebuildTriggers.HasFlag(RebuildTriggers.OnSplineAdded) == false) return;
if (container.GetHashCode() != splineContainer.GetHashCode())
return;
//Debug.Log("OnSplineContainerAdded");
ValidateData(container);
Rebuild(ChangeFlags.Spline);
}
///
/// Ensures that the component has Scale/Transparency/Foam data set up for every spline, and there's a default value point at the start & end.
///
///
public void ValidateData(SplineContainer container)
{
#if MATHEMATICS
int splineCount = container.Splines.Count;
if (ScaleData.Count < splineCount)
{
var delta = splineCount - ScaleData.Count;
//Debug.Log($"OnSplineContainerAdded. Adding {delta} new data");
for (var i = 0; i < delta; i++)
{
#if UNITY_EDITOR
UnityEditor.Undo.RecordObject(this, "Modifying river Scale");
#endif
float length = container.Splines[i].CalculateLength(container.transform.localToWorldMatrix);
SplineData data = new SplineData();
data.DefaultValue = Vector3.one;
data.PathIndexUnit = PathIndexUnit.Distance;
data.AddDataPointWithDefaultValue(0f);
data.AddDataPointWithDefaultValue(length);
ScaleData.Add(data);
}
}
if (TransparencyData.Count < splineCount)
{
var delta = splineCount - TransparencyData.Count;
for (var i = 0; i < delta; i++)
{
#if UNITY_EDITOR
UnityEditor.Undo.RecordObject(this, "Modifying river Transparency");
#endif
float length = container.Splines[i].CalculateLength(container.transform.localToWorldMatrix);
SplineData data = new SplineData();
data.DefaultValue = 0f;
data.PathIndexUnit = PathIndexUnit.Distance;
data.AddDataPointWithDefaultValue(0f);
data.AddDataPointWithDefaultValue(length);
TransparencyData.Add(data);
}
}
if (FoamData.Count < splineCount)
{
var delta = splineCount - FoamData.Count;
for (var i = 0; i < delta; i++)
{
#if UNITY_EDITOR
UnityEditor.Undo.RecordObject(this, "Modifying river Foam");
#endif
float length = container.Splines[i].CalculateLength(container.transform.localToWorldMatrix);
SplineData data = new SplineData();
data.DefaultValue = 0.5f;
data.PathIndexUnit = PathIndexUnit.Distance;
data.AddDataPointWithDefaultValue(0f);
data.AddDataPointWithDefaultValue(length);
FoamData.Add(data);
}
}
#endif
}
[ContextMenu("Reset spline Transparency")]
public void ResetTransparencyData()
{
if (!splineContainer) return;
TransparencyData.Clear();
ValidateData(splineContainer);
Rebuild(ChangeFlags.Data);
}
[ContextMenu("Reset spline Scale")]
public void ResetDisplacementData()
{
if (!splineContainer) return;
ScaleData.Clear();
ValidateData(splineContainer);
Rebuild(ChangeFlags.Data);
}
[ContextMenu("Reset spline Foam")]
public void ResetFoamData()
{
if (!splineContainer) return;
FoamData.Clear();
ValidateData(splineContainer);
Rebuild(ChangeFlags.Data);
}
void OnSplineRemoved(SplineContainer container, int index)
{
if (rebuildTriggers.HasFlag(RebuildTriggers.OnSplineRemoved) == false) return;
if (container != splineContainer)
return;
if (index < ScaleData.Count)
{
#if UNITY_EDITOR
UnityEditor.Undo.RecordObject(this, "Deleting river Scale data");
#endif
ScaleData.RemoveAt(index);
}
if (index < TransparencyData.Count)
{
#if UNITY_EDITOR
UnityEditor.Undo.RecordObject(this, "Deleting river Transparency data");
#endif
TransparencyData.RemoveAt(index);
}
if (index < FoamData.Count)
{
#if UNITY_EDITOR
UnityEditor.Undo.RecordObject(this, "Deleting river Foam data");
#endif
FoamData.RemoveAt(index);
}
Rebuild(ChangeFlags.Data);
}
#endif
}
}