Rasagar/Assets/ProceduralTerrainPainter/Runtime/TerrainPainter.cs

252 lines
8.4 KiB
C#
Raw Permalink Normal View History

2024-08-26 13:07:20 -07:00
// Procedural Terrain Painter by Staggart Creations http://staggart.xyz
// Copyright protected under Unity Asset Store EULA
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
#if __MICROSPLAT__
using JBooth.MicroSplat;
#endif
namespace sc.terrain.proceduralpainter
{
[ExecuteInEditMode]
public partial class TerrainPainter : MonoBehaviour
{
public const string Version = "1.0.4";
public static TerrainPainter Current;
public Terrain[] terrains;
[Attributes.ResolutionDropdown(64, 1024)]
public int splatmapResolution = 256;
[Attributes.ResolutionDropdown(16, 2048)]
[Tooltip("The color/base map is a pre-rendered texture for the terrain color. This is shown on the terrain in the distance. High resolutions usually have little benefit")]
public int colorMapResolution = 256;
public List<LayerSettings> layerSettings = new List<LayerSettings>();
/// <summary>
/// Repaints a terrain if its heightmap is modified. The SetAutoRepaint function must be used to set this up for the terrains. In the editor, the inspector automatically handles this
/// </summary>
[Tooltip("Automatically repaint the terrains if their heightmap is modified. Repaints when the left-mouse button is released")]
public bool autoRepaint;
public List<TerrainChangeListener> terrainListeners = new List<TerrainChangeListener>();
public Bounds bounds;
#if VEGETATION_STUDIO_PRO
[Tooltip("Refreshes the vegetation systems after painting. If vegetation items use terrain layers masks, this is useful")]
public bool refreshVegetationOnPaint;
#endif
#if __MICROSPLAT__
[Tooltip("Assign the TextureArrayConfig asset here. Adding, removing or re-ordering layers will be also be applied to the texture array")]
public TextureArrayConfig msTexArray;
#endif
[SerializeField]
//Reference it once here, so it gets included in a build
private Shader filterShader;
public delegate void TerrainRepaintEvent(Terrain terrain);
/// <summary>
/// Triggers whenever a terrain is repainted. Passes the context terrain as a parameter.
/// </summary>
public static event TerrainRepaintEvent OnTerrainRepaint;
private void OnEnable()
{
if (!filterShader) filterShader = Shader.Find("Hidden/TerrainPainter/Modifier");
Current = this;
}
/// <summary>
/// Applies the splatmapResolution value to all terrains. This must be called when changing the resolution before repainting a single terrain. Automatically done in the <see cref="RepaintAll"/> function
/// </summary>
public void ResizeSplatmaps()
{
//Needs to happen before repainting, all terrains must have the same splatmap resolution. PaintContext throws warnings otherwise
foreach (Terrain terrain in terrains)
{
if(terrain) terrain.terrainData.alphamapResolution = splatmapResolution;
}
}
public void RecalculateBounds()
{
bounds = Utilities.RecalculateBounds(terrains);
}
[ContextMenu("Assign active terrains")]
public void AssignActiveTerrains()
{
SetTargetTerrains(Terrain.activeTerrains);
RepaintAll();
}
public void SetTargetTerrains(Terrain[] terrains)
{
this.terrains = terrains;
RecalculateBounds();
}
/// <summary>
/// Repaints all the assigned terrains using the current configuration
/// </summary>
public void RepaintAll()
{
if (layerSettings.Count == 0) return;
ResizeSplatmaps();
foreach (Terrain terrain in terrains)
{
if (!terrain)
{
Debug.LogError("Missing terrain assigned to TerrainPainter", this);
continue;
}
RepaintTerrain(terrain);
}
//ApplyAllStamps();
}
/// <summary>
/// Repaints an individual terrain
/// </summary>
/// <param name="terrain"></param>
public void RepaintTerrain(Terrain terrain)
{
if (layerSettings.Count == 0 || terrain == null) return;
ModifierStack.Configure(terrain, bounds, splatmapResolution);
ModifierStack.ProcessLayers(terrain, layerSettings);
ApplyStampsToTerrain(terrain);
//Regenerate basemap
terrain.terrainData.baseMapResolution = colorMapResolution;
terrain.terrainData.SetBaseMapDirty();
RefreshVegetation(terrain);
terrain.Flush();
OnTerrainRepaint?.Invoke(terrain);
#if UNITY_EDITOR
EditorUtility.SetDirty(terrain.terrainData);
#endif
}
private void RefreshVegetation(Terrain terrain)
{
#if VEGETATION_STUDIO_PRO
AwesomeTechnologies.VegetationStudio.VegetationStudioManager manager = AwesomeTechnologies.VegetationStudio.VegetationStudioManager.Instance;
if (refreshVegetationOnPaint && manager)
{
#if UNITY_2019_1_OR_NEWER
//VS requires the CPU data
terrain.terrainData.SyncTexture(TerrainData.AlphamapTextureName);
#endif
foreach (AwesomeTechnologies.VegetationSystem.VegetationSystemPro sys in manager.VegetationSystemList)
{
if (sys == null) continue;
sys.ClearCache();
sys.RefreshTerrainArea(terrain.terrainData.bounds);
}
#if UNITY_EDITOR
SceneView.RepaintAll();
#endif
}
#endif
}
public void CreateSettingsForLayer(TerrainLayer layer)
{
LayerSettings s = new LayerSettings();
s.layer = layer;
s.modifierStack = new List<Modifier>();
layerSettings.Insert(0, s);
SetTerrainLayers();
}
/// <summary>
/// Adds or removes the TerrainChangeListener component from all assigned terrains. If enabled, terrains will be repainted when their height is modified
/// </summary>
/// <param name="value"></param>
public void SetAutoRepaint(bool value)
{
autoRepaint = value;
if (value)
{
RemoveTerrainListeners();
foreach (Terrain terrain in terrains)
{
TerrainChangeListener listener = terrain.GetComponent<TerrainChangeListener>();
if (!listener) listener = terrain.gameObject.AddComponent<TerrainChangeListener>();
listener.terrain = terrain;
this.terrainListeners.Add(listener);
}
}
else
{
RemoveTerrainListeners();
}
}
private void RemoveTerrainListeners()
{
for (int i = 0; i < terrainListeners.Count; i++)
{
DestroyImmediate(terrainListeners[i]);
}
terrainListeners.Clear();
}
/// <summary>
/// Ensures that all configured layers are in fact assigned to the terrains. Also removed if they were.
/// </summary>
[ContextMenu("Set terrain layers")]
public void SetTerrainLayers()
{
TerrainLayer[] layers = Utilities.SettingsToLayers(layerSettings);
foreach (Terrain terrain in terrains)
{
terrain.terrainData.terrainLayers = layers;
terrain.terrainData.SetBaseMapDirty();
#if UNITY_EDITOR
EditorUtility.SetDirty(terrain.terrainData);
#endif
}
}
#region Virtual
//Only should be called after the modifiers have been applied, otherwise it acts as a persistent brush
private void ApplyStampsToTerrain(Terrain terrain)
{
ApplyStamps(terrain);
}
partial void ApplyAllStamps();
partial void ApplyStamps(Terrain terrain);
#endregion
}
}