// 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 UnityEngine; using UnityEngine.Events; using UnityEngine.Rendering; #if MATHEMATICS using Unity.Mathematics; #endif #if SPLINES using UnityEngine.Splines; #endif namespace sc.modeling.river.runtime { /// /// Front-end MonoBehaviour component /// [AddComponentMenu("Water/River Modeler")] [HelpURL("https://staggart.xyz/river-modeler-docs/?section=river-modeler")] [ExecuteInEditMode] public partial class RiverModeler : MonoBehaviour { /// /// Flags used to indicate which aspects of a river is being modified /// [Flags] public enum ChangeFlags { All = 1, Spline = 2, Geometry = 4, VertexAttribute = 8, Foam = 16, Scale = 32, Transparency = 64, Data = 128 } [Flags] public enum RebuildTriggers { [InspectorName("Via scripting")] None = 0, OnSplineChanged = 1, OnSplineAdded = 2, OnSplineRemoved = 4, [InspectorName("On Start()")] OnStart = 8, OnUIChange = 16, OnSplineTransformChange = 32 } [Tooltip("Control which sort of events cause the mesh to be regenerated." + "\n\n" + "For instance when the spline changes (default), or on the component's Start() function." + "\n\n" + "If none are selected you need to call the Rebuild() function through script.")] public RebuildTriggers rebuildTriggers = RebuildTriggers.OnSplineAdded | RebuildTriggers.OnSplineRemoved | RebuildTriggers.OnSplineChanged | RebuildTriggers.OnUIChange | RebuildTriggers.OnSplineTransformChange; [Tooltip("The target mesh filter the generated mesh is stored in. Attaching a Mesh Renderer to the same object will render it using a material")] public MeshFilter meshFilter; [Range(-3, 3)] [Tooltip("Sets the rendering offset for the Mesh Renderer component." + "\n\n" + "This relates directly to transparency sorting and may be used to force one river to render on top of another one." + "\n\n" + "For this to have any effect, the renderer must used a shader that doesn't write to the depth buffer (ZWrite Off) ")] public int orderInLayer = 0; /* LOD Group support WIP public LODGroup lodGroup; [Serializable] public class LOD { [Range(5f, 100f)] public float resolution = 100f; public LOD(float resolution) { this.resolution = resolution; } } public List lods = new List() { new LOD(100f) }; */ /// /// Settings used for geometry generation /// public Settings settings = new Settings(); public bool exportToFBX; public string fbxFilePath; #pragma warning disable CS0067 public delegate void OnRebuildRiver(RiverModeler instance, ChangeFlags updateFlags); public static event OnRebuildRiver onPreRebuildRiver; public static event OnRebuildRiver onPostRebuildRiver; /// /// UnityEvent for a GameObject's function to be executed when river is rebuild. This is exposed in the inspector. /// [Serializable] public class RebuildEvent : UnityEvent { } /// /// UnityEvent, fires whenever spline is rebuild (eg. editing nodes) /// public RebuildEvent onPreRebuild; public RebuildEvent onPostRebuild; #pragma warning restore CS0067 public void Reset() { #if !SPLINES Debug.LogError("This component requires the Splines package. Please install this through the Package Manager"); #endif #if !MATHEMATICS Debug.LogError("This component requires the Mathematics package. Please install this through the Package Manager"); #endif meshFilter = GetComponent(); #if SPLINES && MATHEMATICS if(!splineContainer) splineContainer = GetComponentInParent(); ValidateData(splineContainer); #endif Rebuild(); } private void OnEnable() { #if SPLINES SubscribeSplineCallbacks(); #endif } private void OnDisable() { #if SPLINES UnsubscribeSplineCallbacks(); #endif } private void Start() { //In this case the component was likely copied somewhere, or prefabbed. Mesh data will have been lost, so regenerating it is an alternative if (rebuildTriggers.HasFlag(RebuildTriggers.OnStart)) Rebuild(); } #if SPLINES private partial void SubscribeSplineCallbacks(); private partial void UnsubscribeSplineCallbacks(); #endif public void Rebuild(ChangeFlags updateFlags = ChangeFlags.All) { //Debug.Log($"Rebuilding river with update flags: {updateFlags}", this); if (!meshFilter) return; onPreRebuildRiver?.Invoke(this, updateFlags); onPreRebuild?.Invoke(updateFlags); settings.foam.Validate(); #if SPLINES && MATHEMATICS if (!splineContainer) return; ValidateData(splineContainer); if(updateFlags.HasFlag(ChangeFlags.All | ChangeFlags.Spline)) ValidateData(splineContainer); /* LOD Group support WIP //Single mesh if (lods.Count == 1 && meshFilter) { meshFilter.sharedMesh = Geometry.Create(splineContainer, ScaleData, OpacityData, generationSettings, meshFilter.transform.worldToLocalMatrix); } //LOD set up if (lods.Count > 1 && lodGroup) { UnityEngine.LOD[] lodGroupLODS = lodGroup.GetLODs(); for (int i = 0; i < lodGroupLODS.Length; i++) { MeshFilter lodMF = lodGroupLODS[i].renderers[0].GetComponent(); if (lodMF) { lodMF.sharedMesh = Geometry.Create(splineContainer, ScaleData, OpacityData, generationSettings, meshFilter.transform.worldToLocalMatrix, lods[i].resolution); Debug.Log($"Creating LOD {i} at {lods[i].resolution}% for {lodMF.gameObject.name}"); } } } */ meshFilter.sharedMesh = Geometry.Create(splineContainer, settings, meshFilter.transform.worldToLocalMatrix, 100f, ScaleData, TransparencyData, FoamData); MeshRenderer meshRenderer = meshFilter.GetComponent(); if (meshRenderer) { meshRenderer.sortingOrder = orderInLayer; meshRenderer.shadowCastingMode = ShadowCastingMode.Off; if (settings.transparency.vertexColorChannel == Settings.VertexColorChannel.Green) { //If the material uses Stylized Water 2, enable vertex color transparency. Quickfix to avoid support overhead var vertexColorAlphaProp = Shader.PropertyToID("_VertexColorDepth"); if (meshRenderer.sharedMaterial.HasProperty(vertexColorAlphaProp)) { if (meshRenderer.sharedMaterial.GetFloat(vertexColorAlphaProp) == 0) { Debug.Log($"[River Modeler] A Stylized Water 2 material is in use, yet vertex color Transparency wasn't enabled on the material. Enabled now.", this.gameObject); meshRenderer.sharedMaterial.SetFloat(vertexColorAlphaProp, 1); #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(meshRenderer.sharedMaterial); #endif } } //If the material uses Stylized Water 3, enable vertex color transparency. Quickfix to avoid support overhead vertexColorAlphaProp = Shader.PropertyToID("_VertexColorTransparency"); if (meshRenderer.sharedMaterial.HasProperty(vertexColorAlphaProp)) { if (meshRenderer.sharedMaterial.GetFloat(vertexColorAlphaProp) == 0) { Debug.Log($"[River Modeler] A Stylized Water 3 material is in use, yet vertex color Transparency wasn't enabled on the material. Enabled now.", this.gameObject); meshRenderer.sharedMaterial.SetFloat(vertexColorAlphaProp, 1); #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(meshRenderer.sharedMaterial); #endif } } } if (settings.transparency.vertexColorChannel == Settings.VertexColorChannel.Alpha) { //If the material uses Stylized Water, enable vertex color foam. Quickfix to avoid support overhead var vertexColorFoamProp = Shader.PropertyToID("_VertexColorFoam"); if (meshRenderer.sharedMaterial.HasProperty(vertexColorFoamProp)) { if (meshRenderer.sharedMaterial.GetFloat(vertexColorFoamProp) == 0) { Debug.Log($"[River Modeler] A Stylized Water material is in use, yet vertex color Foam wasn't enabled on the material. Enabled now.", this.gameObject); meshRenderer.sharedMaterial.SetFloat(vertexColorFoamProp, 1); #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(meshRenderer.sharedMaterial); #endif } } } } //Update mesh collider MeshCollider meshCollider = GetComponent(); if (meshCollider) meshCollider.sharedMesh = meshFilter.sharedMesh; onPostRebuildRiver?.Invoke(this, updateFlags); onPostRebuild?.Invoke(updateFlags); #if MICROVERSE_SPLINES if (updateFlags.HasFlag(ChangeFlags.All) || updateFlags.HasFlag(ChangeFlags.Spline) || updateFlags.HasFlag(ChangeFlags.Geometry) || updateFlags.HasFlag(ChangeFlags.Data)) { //MicroVerse syncing UpdateMicroVerseSpline(); } #endif #endif } public partial void UpdateMicroVerseSpline(); partial void DrawDebugging(); private void OnDrawGizmosSelected() { DrawDebugging(); #if SPLINES && MATHEMATICS //Moving the spline, then selecting the river again if (splineContainer && splineContainer.transform.hasChanged) { if (rebuildTriggers.HasFlag(RebuildTriggers.OnSplineTransformChange)) { splineContainer.transform.hasChanged = false; Rebuild(); } } #endif } } }