using System.Collections.Generic; using UnityEngine; namespace Assets.ThoMagic.Renderer { public class GrassRenderer : GrassStreamer { private readonly Terrain terrain; private readonly TerrainData terrainData; private bool isDirty; public GrassRenderer(Terrain terrain) : base(terrain) { this.terrain = terrain; terrainData = terrain.terrainData; } public void OnTerrainChanged(TerrainChangedFlags flags) { if (flags.HasFlag(TerrainChangedFlags.DelayedHeightmapUpdate)) isDirty = true; else if (flags.HasFlag(TerrainChangedFlags.Heightmap)) isDirty = true; else if (flags.HasFlag(TerrainChangedFlags.HeightmapResolution)) isDirty = true; else if (flags.HasFlag(TerrainChangedFlags.Holes)) isDirty = true; else if (flags.HasFlag(TerrainChangedFlags.DelayedHolesUpdate)) isDirty = true; else if (flags.HasFlag(TerrainChangedFlags.FlushEverythingImmediately)) isDirty = true; } public void Destroy() { Recycle(); } public void LateUpdate() { if (isDirty) { isDirty = false; Load(); } if (Application.isEditor && !Application.isPlaying) RebuildChangedPrototypes(); Update(); } public void Load() { DetailPrototype[] detailPrototypes = terrainData.detailPrototypes; Recycle(); int ownerHash = GetHashCode(); if (grassPrefabs == null) { grassPrefabs = new List(); grassPrefabSet = new List(); } float maxDetailDistance = float.MinValue; for (int index = 0; index < detailPrototypes.Length; ++index) { if (!detailPrototypes[index].usePrototypeMesh || detailPrototypes[index].prototype == null) { AddDummy(index); } else { GameObject prototypeToRender = RendererUtility.GetPrototypeToRender(detailPrototypes[index]); if (!RendererUtility.SupportsProceduralInstancing(prototypeToRender)) { AddDummy(index); } else { var settings = GetSettingsOrDefault(prototypeToRender); maxDetailDistance = Mathf.Max(maxDetailDistance, settings.Settings.RenderDistance); grassPrefabs.Add(RendererPool.RegisterObject(prototypeToRender, settings, this, ownerHash)); grassPrefabSet.Add(true); } } } Build(maxDetailDistance, detailPrototypes.Length); } private void AddDummy(int i) { grassPrefabs.Add(-1); grassPrefabSet.Add(false); } public override void Recycle() { if (grassPrefabs == null) return; base.Recycle(); int ownerHash = GetHashCode(); for (int index = 0; index < grassPrefabs.Count; ++index) { if (grassPrefabSet[index]) { RendererPool.RemoveObject(grassPrefabs[index], ownerHash); } } grassPrefabs.Clear(); grassPrefabSet.Clear(); } private void RebuildChangedPrototypes() { DetailPrototype[] detailPrototypes = terrainData.detailPrototypes; if (detailPrototypes.Length != grassPrefabs.Count) { Load(); } else { int ownerHash = GetHashCode(); for (int index = 0; index < grassPrefabs.Count; ++index) { if (grassPrefabSet[index]) { GameObject prototypeToRender = RendererUtility.GetPrototypeToRender(detailPrototypes[index]); GameObject gameObject = RendererPool.GetObject(grassPrefabs[index]); if (prototypeToRender != gameObject) { RendererPool.RemoveObject(grassPrefabs[index], ownerHash); if (prototypeToRender != null) { grassPrefabs[index] = RendererPool.RegisterObject(prototypeToRender, GetSettingsOrDefault(prototypeToRender), this, ownerHash); grassPrefabSet[index] = true; } else { grassPrefabs[index] = -1; grassPrefabSet[index] = false; } } else if (RendererPool.ContentHashChanged(grassPrefabs[index])) { RendererPool.RemoveObject(grassPrefabs[index], GetHashCode()); if (prototypeToRender != null) { grassPrefabs[index] = RendererPool.RegisterObject(prototypeToRender, GetSettingsOrDefault(prototypeToRender), this, ownerHash); grassPrefabSet[index] = true; } else { grassPrefabs[index] = -1; grassPrefabSet[index] = false; } } else if (prototypeToRender != null) RendererPool.SetObjectSettings(prototypeToRender, GetSettingsOrDefault(prototypeToRender)); } } } } private IInstanceRenderSettings GetSettingsOrDefault(GameObject gameObject) { IInstanceRenderSettings instanceRenderSettings; return gameObject.TryGetComponent(out instanceRenderSettings) ? instanceRenderSettings : new DefaultRenderSettings(); } private class DefaultRenderSettings : IInstanceRenderSettings { public InstanceRenderSettings Settings => new InstanceRenderSettings() { Render = true, DensityInDistance = 0.125f, DensityInDistanceFalloff = new Vector2(0.08f, 0.0075f), RenderDistance = 150f, ShadowDistance = 50f, Shadows = true, Supported = true }; } } }