//Copyright(c)2020 Procedural Worlds Pty Limited using System; using System.Collections.Generic; using UnityEngine; namespace GeNa.Core { /// /// Spline Extension for creating Rivers along a Spline /// [Serializable] [CreateAssetMenu(fileName = "Rivers", menuName = "Procedural Worlds/GeNa/Extensions/Rivers", order = 4)] public class GeNaRiverExtension : GeNaSplineExtension { const string GENA_USER_DATA_PATH = "GeNa User Data/"; const string BAKED_UNSPLIT_RIVER_PARENT_NAME = "BakedUnsplit_RiverMeshes"; const string RIVER_MESHES_PARENT_NAME = "RiverMeshes"; [SerializeField] protected GeNaRiverProfile m_riverProfile; [SerializeField] protected Material m_currentMaterial; [SerializeField] protected float m_seaLevel = 25f; [SerializeField] protected bool m_useGaiaSeaLevel = true; [SerializeField] protected float m_startFlow = 0.2f; [SerializeField] protected float m_vertexDistance = 3.0f; [SerializeField] protected float m_bankOverstep = 1.0f; [SerializeField] protected float m_splineSmoothing = 0.82f; [SerializeField] protected float m_riverWidth = 20f; [SerializeField] protected float m_capDistance = 15f; [SerializeField] protected float m_endCapDistance = 0.0f; [SerializeField] protected bool m_addCollider = false; [SerializeField] protected bool m_shadowsCast = false; [SerializeField] protected bool m_shadowsReceive = false; [SerializeField] protected bool m_raycastTerrainOnly = true; [SerializeField] protected bool m_useWorldspaceTextureWidth = false; [SerializeField] protected float m_worldspaceWidth = 15.0f; [SerializeField] protected string m_tag = "Untagged"; [SerializeField] protected int m_layer = -1; [SerializeField] protected bool m_splitAtTerrains = true; [SerializeField] protected bool m_autoUpdateOnTerrainChange = false; [SerializeField] protected MeshRenderer m_meshRenderer; [NonSerialized] protected RenderTexture m_currentRenderTexture = null; [NonSerialized] protected GameObject m_riverMeshParent = null; [NonSerialized] protected bool m_postProcess = true; [SerializeField] protected List m_bakedMeshes = new List(); public GeNaRiverProfile RiverProfile { get => m_riverProfile; set => m_riverProfile = value; } public bool UseGaiaSeaLevel { get => m_useGaiaSeaLevel; set => m_useGaiaSeaLevel = value; } public float SeaLevel { get => m_seaLevel; set => m_seaLevel = value; } public bool SyncToWeather { get => m_riverProfile.RiverParameters.m_syncToWeather; set => m_riverProfile.RiverParameters.m_syncToWeather = value; } public float StartFlow { get => m_startFlow; set => m_startFlow = Mathf.Clamp(value, 0.05f, Mathf.Infinity); } public float VertexDistance { get => m_vertexDistance; set => m_vertexDistance = value; } public float BankOverstep { get => m_bankOverstep; set => m_bankOverstep = Mathf.Clamp(value, 0.5f, 5.0f); } public float RiverWidth { get => m_riverWidth; set => m_riverWidth = Mathf.Max(value, 0.5f); } public float CapDistance { get => m_capDistance; set => m_capDistance = Mathf.Clamp(value, 0.1f, Mathf.Infinity); } public float EndCapDistance { get => m_endCapDistance; set => m_endCapDistance = Mathf.Clamp(value, 0.0f, 5000.0f); } public bool AddCollider { get => m_addCollider; set => m_addCollider = value; } public bool RaycastTerrainOnly { get => m_raycastTerrainOnly; set => m_raycastTerrainOnly = value; } public bool ReceiveShadows { get => m_shadowsReceive; set => m_shadowsReceive = value; } public bool CastShadows { get => m_shadowsCast; set => m_shadowsCast = value; } private void OnEnable() { if (m_autoUpdateOnTerrainChange) { GeNaEvents.onTerrainChanged -= TerrainChanged; GeNaEvents.onTerrainChanged += TerrainChanged; } } public bool UpdateOnTerrainChange { get => m_autoUpdateOnTerrainChange; set { if (m_autoUpdateOnTerrainChange != value) { if (value) { GeNaEvents.onTerrainChanged -= TerrainChanged; GeNaEvents.onTerrainChanged += TerrainChanged; } else { GeNaEvents.onTerrainChanged -= TerrainChanged; } } m_autoUpdateOnTerrainChange = value; } } public string Tag { get => m_tag; set => m_tag = value; } public int Layer { get => m_layer; set => m_layer = value; } public bool SplitAtTerrains { get => m_splitAtTerrains; set => m_splitAtTerrains = value; } public bool UseWorldspaceTextureWidth { get => m_useWorldspaceTextureWidth; set => m_useWorldspaceTextureWidth = value; } public float WorldspaceWidthRepeat { get => m_worldspaceWidth; set => m_worldspaceWidth = value; } public MeshRenderer MeshRenderer { get => m_meshRenderer; set => m_meshRenderer = value; } public GameObject Parent { get => m_riverMeshParent; set => m_riverMeshParent = value; } /// /// GeNa Extension Methods /// #region GeNaSpline Extension Methods protected override void OnAttach(GeNaSpline spline) { if (RiverProfile == null) { RiverProfile = GeNaUtility.LoadNewRiverProfile(); if (m_layer < 0) { m_layer = LayerMask.NameToLayer("PW_Object_Large"); if (m_layer < 0) m_layer = 0; } } if (m_autoUpdateOnTerrainChange) { GeNaEvents.onTerrainChanged -= TerrainChanged; GeNaEvents.onTerrainChanged += TerrainChanged; } CreateRivers(); GameObjectEntity gameObjectEntity = ScriptableObject.CreateInstance(); gameObjectEntity.m_gameObject = m_riverMeshParent; GeNaUndoRedo.RecordUndo(gameObjectEntity); } List affectedTerrains = new List(); private void TerrainChanged(Terrain terrain, TerrainChangedFlags flags) { if ((flags & TerrainChangedFlags.Heightmap) == TerrainChangedFlags.Heightmap || (flags & TerrainChangedFlags.FlushEverythingImmediately) == TerrainChangedFlags.FlushEverythingImmediately) { bool affected = false; Transform terrainTransform = terrain.transform; for (int i = 0; i < affectedTerrains.Count; i++) { if (affectedTerrains[i] == terrainTransform) affected = true; } if (!affected) return; //Debug.Log($"Affected Terrain Changed ({Spline.Nodes.Count})."); PreExecute(); Execute(); } } public void Bake(bool postProcess) { m_postProcess = postProcess; Bake(); } protected override GameObject OnBake(GeNaSpline spline) { if (m_postProcess) { PreExecute(); Execute(); } else { CreateCurrentRenderTexture(); } FindMeshesParent(); if (m_autoUpdateOnTerrainChange) GeNaEvents.onTerrainChanged -= TerrainChanged; if (m_bakedMeshes == null) m_bakedMeshes = new List(); if (m_bakedMeshes.Count > 0) { for (int i = 0; i < m_bakedMeshes.Count; i++) { if (m_bakedMeshes[i] != null) GeNaEvents.Destroy(m_bakedMeshes[i]); } } m_bakedMeshes.Clear(); spline.IsDirty = true; GameObject riverMeshes = GeNaEvents.BakeSpline(m_riverMeshParent, spline); if (riverMeshes == null) return null; Dictionary> meshParentDict = null; if (m_postProcess && m_splitAtTerrains) { // PostProcess will split the meshes up into new meshes per terrain. List meshTransforms = GeNaRiverMesh.PostProcess(riverMeshes, BakedGroupName()); meshParentDict = ProcessRenderTargets(meshTransforms); if (!GeNaEvents.HasTerrainsAsScenes()) { foreach (Transform parentTransform in meshParentDict.Keys) { m_bakedMeshes.Add(parentTransform.gameObject); } } } else { GameObject bakedRivers = GameObject.Find(BAKED_UNSPLIT_RIVER_PARENT_NAME); if (bakedRivers == null) { bakedRivers = new GameObject(BAKED_UNSPLIT_RIVER_PARENT_NAME); } riverMeshes.transform.parent = bakedRivers.transform; riverMeshes.name = BakedGroupName(); List meshTransforms = new List(); Renderer[] renderers = riverMeshes.GetComponentsInChildren(); for (int i = 0; i < renderers.Length; i++) meshTransforms.Add(renderers[i].transform); meshParentDict = ProcessRenderTargets(meshTransforms); if (m_postProcess) { foreach (Transform parentTransform in meshParentDict.Keys) { m_bakedMeshes.Add(parentTransform.gameObject); } } } return riverMeshes; } void FindMeshesParent() { if (m_riverMeshParent != null && m_riverMeshParent.transform.parent != Spline.transform) m_riverMeshParent = null; if (m_riverMeshParent == null) { Transform parent = Spline.gameObject.transform.Find(RIVER_MESHES_PARENT_NAME); if (parent != null) m_riverMeshParent = parent.gameObject; } } Dictionary> ProcessRenderTargets(List meshTransforms) { Dictionary> meshParentDict = new Dictionary>(); if (meshTransforms.Count < 1) return meshParentDict; foreach (Transform meshXform in meshTransforms) { if (!meshParentDict.ContainsKey(meshXform.parent)) meshParentDict.Add(meshXform.parent, new List()); meshParentDict[meshXform.parent].Add(meshXform); } // Get the Flow Render Target and Create a Texture2D from it, // to be assigned to the River Material. if (RiverProfile.RiverParameters.m_renderMode == Constants.ProfileRenderMode.RiverFlow) { Material material = meshTransforms[0].GetComponent().sharedMaterial; if (m_currentMaterial != null) material = m_currentMaterial; // Create a Texture2D to replace the RenderTexture created in CaptureRiverFlow. Texture tex = material.GetTexture("_FlowMap"); RenderTexture rtFlow = tex as RenderTexture; if (rtFlow != null) { RenderTexture.active = rtFlow; int size = rtFlow.width; Texture2D tFlow = new Texture2D(size, size, TextureFormat.RGBA32, false, true); tFlow.anisoLevel = 0; tFlow.ReadPixels(new Rect(0, 0, size, size), 0, 0, false); tFlow.Apply(); RenderTexture.active = null; material.SetTexture("_FlowMap", tFlow); foreach (List meshes in meshParentDict.Values) { Material newMat = new Material(material); newMat.SetTexture("_FlowMap", tFlow); foreach (Transform transform in meshes) { transform.GetComponent().sharedMaterial = newMat; } } } } return meshParentDict; } /// /// Returns a dictionary of mesh transforms key'ed by their parent transform. /// /// /// /// /// /// Dictionary> ProcessRenderTargetsPerTerrain(List meshTransforms) { Dictionary> meshParentDict = new Dictionary>(); foreach (Transform meshXform in meshTransforms) { if (!meshParentDict.ContainsKey(meshXform.parent)) meshParentDict.Add(meshXform.parent, new List()); meshParentDict[meshXform.parent].Add(meshXform); } // If the current River Profile uses the RiverFlow shader, then // we need to capture the flow texture for all of the meshes // that are parented to the same terrain, with the same resolution // texture for each. if (RiverProfile.RiverParameters.m_renderMode == Constants.ProfileRenderMode.RiverFlow) { //Debug.Log($"Creating a Texture2D for each RenderTexture for {terrainMeshDict.Count} terrain rivers."); GameObject oceanGO = GameObject.Find("Water Surface"); Transform oceanMeshTransform = null; if (oceanGO != null) oceanMeshTransform = oceanGO.transform; foreach (List meshXForms in meshParentDict.Values) { // Let's get the "Baked River Meshes" transform so that we capture the flow of all river meshes. if (meshXForms[0].parent == null || meshXForms[0].parent.parent == null) { Debug.LogError("Missing River Mesh parent and/or grandparent."); return meshParentDict; } Transform bakedRiverMeshes = meshXForms[0].parent.parent; Terrain terrain = null; if (bakedRiverMeshes != null && bakedRiverMeshes.parent != null) terrain = bakedRiverMeshes.parent.GetComponent(); Material material = RiverProfile.ApplyProfile(SeaLevel, true, meshXForms[0].GetComponent().sharedMaterial); foreach (Transform meshXForm in meshXForms) { meshXForm.GetComponent().sharedMaterial = material; } CaptureFlow captureFlow = new CaptureFlow(); CaptureFlow.RiverFlowResults results; if (bakedRiverMeshes != null) results = captureFlow.CaptureRiverFlow(bakedRiverMeshes, null, 4096, material, oceanMeshTransform, terrain); else results = captureFlow.CaptureRiverFlow(meshXForms, null, 4096, material, oceanMeshTransform, terrain); material.SetVector("_BoundsMinimum", results.boundsCenter - results.boundsExtent); material.SetVector("_BoundsMaximum", results.boundsCenter + results.boundsExtent); material.SetVector("_Center", results.boundsCenter); material.SetVector("_Extent", results.boundsExtent); // Create a Texture2D to replace the RenderTexture created in CaptureRiverFlow. RenderTexture.active = results.rtFlow; int size = results.rtFlow.width; Texture2D tFlow = new Texture2D(size, size, TextureFormat.RGBA32, false, true); tFlow.anisoLevel = 0; tFlow.ReadPixels(new Rect(0, 0, size, size), 0, 0, false); tFlow.Apply(); RenderTexture.active = null; // Now, set all material properties of the river meshes that depend on the new Flow Texture. // NOTE: for now, these operations will be done over and over, for each River Spline on a terrain. if (bakedRiverMeshes != null) { Renderer[] allRenderers = bakedRiverMeshes.GetComponentsInChildren(false); foreach (Renderer renderer in allRenderers) { Material thisMaterial = renderer.sharedMaterial; if (thisMaterial.shader == material.shader) { thisMaterial.SetVector("_BoundsMinimum", results.boundsCenter - results.boundsExtent); thisMaterial.SetVector("_BoundsMaximum", results.boundsCenter + results.boundsExtent); thisMaterial.SetVector("_Center", results.boundsCenter); thisMaterial.SetVector("_Extent", results.boundsExtent); thisMaterial.SetTexture("_FlowMap", tFlow); } } } } } return meshParentDict; } public override void Execute() { if (IsActive && Spline.Nodes.Count > 1) { ProcessSpline(Spline); } } public override void PreExecute() { //DeleteRiverMeshGameobjects(Spline); } protected override void OnActivate() { if (Spline.Nodes.Count > 1) ProcessSpline(Spline); if (m_autoUpdateOnTerrainChange) { GeNaEvents.onTerrainChanged -= TerrainChanged; GeNaEvents.onTerrainChanged += TerrainChanged; } } protected override void OnDeactivate() { DeleteRiverMeshGameobjects(Spline); if (m_autoUpdateOnTerrainChange) GeNaEvents.onTerrainChanged -= TerrainChanged; } protected override void OnDelete() { DeleteRiverMeshGameobjects(Spline); if (m_autoUpdateOnTerrainChange) GeNaEvents.onTerrainChanged -= TerrainChanged; if (m_riverMeshParent != null) { GeNaEvents.Destroy(m_riverMeshParent); m_riverMeshParent = null; } } protected override void OnDrawGizmosSelected() { if (Spline.Settings.Advanced.DebuggingEnabled == false) return; foreach (GeNaCurve curve in Spline.Curves) { DrawCurveInfo(curve); } } private void DrawCurveInfo(GeNaCurve geNaCurve) { // Draw arrows showing which direction a curve is facing (from StartNode to EndNode). Gizmos.color = Color.red; GeNaSample geNaSample = geNaCurve.GetSample(0.45f); DrawArrow(geNaSample.Location, geNaSample.Forward); geNaSample = geNaCurve.GetSample(0.5f); DrawArrow(geNaSample.Location, geNaSample.Forward); geNaSample = geNaCurve.GetSample(0.55f); DrawArrow(geNaSample.Location, geNaSample.Forward); } private void DrawArrow(Vector3 position, Vector3 direction) { direction.Normalize(); Vector3 right = Vector3.Cross(Vector3.up, direction).normalized; Ray ray = new Ray(position, (-direction + right) * 0.5f); Gizmos.DrawRay(ray); ray.direction = (-direction - right) * 0.5f; Gizmos.DrawRay(ray); } #endregion End GeNa Extension Methods private void DeleteRiverMeshGameobjects(GeNaSpline spline) { // Check to make sure they haven't move the road meshes in the hierarchy if (m_riverMeshParent != null && m_riverMeshParent.transform.parent != Spline.transform) m_riverMeshParent = null; if (m_riverMeshParent == null) { Transform splineTransform = Spline.gameObject.transform; // see if we can find it Transform riverMeshesTransform = splineTransform.Find(RIVER_MESHES_PARENT_NAME); if (riverMeshesTransform != null) m_riverMeshParent = riverMeshesTransform.gameObject; } if (m_riverMeshParent != null) { List children = new List(); foreach (Transform transform in m_riverMeshParent.transform) { children.Add(transform); } for (int i = 0; i < children.Count; i++) { GeNaEvents.Destroy(children[i].gameObject); } } } private void ProcessSpline(GeNaSpline spline) { CreateRivers(); } public void UpdateMaterial() { if (RiverProfile == null) { return; } // recompute the river meshes and assign new material. ProcessSpline(this.Spline); } private string BakedGroupName() { return $"River Meshes ({this.Spline.GetInstanceID() % 9997})"; } public bool HasBakedRivers() { return m_bakedMeshes != null && m_bakedMeshes.Count > 0; /* string groupName = BakedGroupName(); GameObject go = GameObject.Find(groupName); if (go != null) return true; return false; */ } public void DeleteBakedRiver(bool reenableSpline = false) { //string groupName = BakedGroupName(); //GameObject go = GameObject.Find(groupName); if (m_bakedMeshes == null || m_bakedMeshes.Count < 1) return; int count = 0; //while (go != null) foreach (GameObject go in m_bakedMeshes) { if (go != null) { Transform parent = go.transform.parent; GeNaEvents.Destroy(go); count++; if (parent != null && parent.childCount == 0 && (parent.name == "Baked_RiverMeshes" || parent.name == BAKED_UNSPLIT_RIVER_PARENT_NAME)) { GeNaEvents.Destroy(parent.gameObject); } } //go = GameObject.Find(groupName); } if (reenableSpline) Spline.gameObject.SetActive(true); m_bakedMeshes.Clear(); Spline.IsDirty = true; if (count > 0) Debug.Log($"{count} baked river mesh group(s) deleted."); } public void SetSplineToDownhill() { if (Spline == null) return; _SetSplineToDownhill(); } private void _SetSplineToDownhill() { Dictionary> trees = Spline.GetTrees(); foreach (List curCurves in trees.Values) { (float min, float max) minMax = _GetCurvesMinMax(curCurves); List curves = new List(curCurves); if (minMax.min > minMax.max) { curves.Reverse(); (minMax.min, minMax.max) = (minMax.max, minMax.min); } float curHeight = minMax.max; for (int i = 0; i < curves.Count; i++) { if (curves[i].EndNode.Position.y >= curHeight) { Vector3 pos = curves[i].EndNode.Position; pos = new Vector3(pos.x, curHeight - 0.001f, pos.z); curves[i].EndNode.Position = pos; } curHeight = curves[i].EndNode.Position.y; } } Spline.Smooth(); } private (float min, float max) _GetCurvesMinMax(List curves) { return (curves[curves.Count - 1].P3.y, curves[0].P0.y); } private void CreateRivers() { if (Spline == null || Spline.Nodes.Count < 2) return; // See if we already have a material Material material = null; if (m_riverMeshParent != null) { Renderer child = m_riverMeshParent.GetComponentInChildren(); if (child != null) { material = child.sharedMaterial; if (!material.HasProperty("_FlowMap")) { material = m_currentMaterial; } } } DeleteRiverMeshGameobjects(Spline); if (m_riverMeshParent == null) { Transform splineTransform = Spline.gameObject.transform; // see if we can find it Transform riverMeshesTransform = splineTransform.Find(RIVER_MESHES_PARENT_NAME); if (riverMeshesTransform != null) m_riverMeshParent = riverMeshesTransform.gameObject; if (m_riverMeshParent == null) { m_riverMeshParent = new GameObject(RIVER_MESHES_PARENT_NAME); m_riverMeshParent.transform.position = Vector3.zero; m_riverMeshParent.transform.parent = splineTransform; } } if (RiverProfile != null) { if (GeNaUtility.Gaia2Present) { if (UseGaiaSeaLevel) { SeaLevel = GeNaEvents.GetSeaLevel(SeaLevel); } } m_currentMaterial = RiverProfile.ApplyProfile(SeaLevel, true, material); GeNaRiverMesh geNaRiverMesh = new GeNaRiverMesh(Spline, m_startFlow, m_vertexDistance, m_bankOverstep, m_currentMaterial, m_riverWidth, SyncToWeather, RiverProfile, m_shadowsCast, m_shadowsReceive); if (m_autoUpdateOnTerrainChange) geNaRiverMesh.m_affectedTerrains = affectedTerrains; else geNaRiverMesh.m_affectedTerrains = null; geNaRiverMesh.CreateMeshes(m_riverMeshParent.transform, m_addCollider, m_raycastTerrainOnly, m_tag, m_layer, SeaLevel, CapDistance, UseWorldspaceTextureWidth, WorldspaceWidthRepeat, EndCapDistance); RenderTexture newRTex = CaptureRiverFlowTexture(); if (m_currentRenderTexture != null && m_currentRenderTexture != newRTex) { GeNaEvents.Destroy(m_currentRenderTexture); //Debug.Log("Render Texture for River Material destroyed."); } m_currentRenderTexture = newRTex; } } void CreateCurrentRenderTexture() { FindMeshesParent(); RenderTexture newRTex = CaptureRiverFlowTexture(); if (m_currentRenderTexture != null && m_currentRenderTexture != newRTex) { GeNaEvents.Destroy(m_currentRenderTexture); //Debug.Log("Render Texture for River Material destroyed."); } m_currentRenderTexture = newRTex; } public RenderTexture CaptureRiverFlowTexture(bool saveFlowTexture = false) { if (m_riverMeshParent == null) return m_currentRenderTexture; // Do we need to create a river flow texture? GeNaRiverParameters riverParameters = RiverProfile.RiverParameters; if (riverParameters != null) { if (riverParameters.m_renderMode == Constants.ProfileRenderMode.RiverFlow) { GameObject oceanGO = GameObject.Find("Water Surface"); Transform oceanMeshTransform = null; if (oceanGO != null) oceanMeshTransform = oceanGO.transform; int textureSize = 1024; string filename = string.Empty; if (saveFlowTexture) { filename = System.IO.Path.Combine(Application.dataPath, GENA_USER_DATA_PATH, $"FlowMap_{Spline.GetInstanceID()}.png"); Debug.Log($"Flow Map filename = {filename}"); } else { textureSize = 2048; } CaptureFlow captureFlow = new CaptureFlow(); CaptureFlow.RiverFlowResults results = captureFlow.CaptureRiverFlow(m_riverMeshParent.transform, filename, textureSize, m_currentMaterial, oceanMeshTransform); m_currentMaterial.SetVector("_BoundsMinimum", results.boundsCenter - results.boundsExtent); m_currentMaterial.SetVector("_BoundsMaximum", results.boundsCenter + results.boundsExtent); m_currentMaterial.SetVector("_Center", results.boundsCenter); m_currentMaterial.SetVector("_Extent", results.boundsExtent); return results.rtFlow; } } return null; } } }