using System; using System.Collections; using System.Collections.Generic; using Digger.Modules.Core.Sources; using Digger.Modules.Core.Sources.Operations; using Unity.Jobs; using UnityEngine; namespace Digger.Modules.Runtime.Sources { /// /// To support real-time / in-game editing, add this component into the scene and use its 'Modify' method to edit the terrain. /// /// Example of use [DefaultExecutionOrder(-10)] public class DiggerMasterRuntime : ADiggerRuntimeMonoBehaviour { public bool enablePersistence; private DiggerSystem[] diggerSystems; private bool isRunningAsync; private readonly Queue buffer = new Queue(); private int bufferSize = 1; private readonly BasicOperation basicOperation = new BasicOperation(); private readonly KernelOperation kernelOperation = new KernelOperation(); /// /// True when a modification is currently being done asynchronously. /// public bool IsRunningAsync => isRunningAsync; public int BufferSize { get => bufferSize; set => bufferSize = Math.Max(1, value); } /// /// Modify the terrain at runtime by performing the operation synchronously. /// /// The operation to perform public void Modify(IOperation operation) where T : struct, IJobParallelFor { if (isRunningAsync) { Debug.LogError("Cannot Modify as asynchronous modification is already in progress"); return; } foreach (var diggerSystem in diggerSystems) { diggerSystem.Modify(operation); } } /// /// Modify the terrain at runtime by performing the requested action. /// /// Position where you want to edit the terrain /// Brush type /// Action type /// Index of the texture to be used (starting from 0 to 7). See DiggerMaster inspector to know what texture corresponds to an index. /// Strength/intensity of edit /// Size of edit /// Height of stalagmite (only when Brush is stalagmite) /// Defines if stalagmite is upside-down or not (only when Brush is stalagmite) /// If true when painting texture, the weight of the texture will be directly set to the given opacity public void Modify(Vector3 position, BrushType brush, ActionType action, int textureIndex, float opacity, float size, float stalagmiteHeight = 8f, bool stalagmiteUpsideDown = false, bool opacityIsTarget = false) { if (action == ActionType.Smooth && brush != BrushType.Sphere) { Debug.LogError("Smooth action only supports Sphere brush"); return; } var p = new ModificationParameters { Position = position, Brush = brush, Action = action, TextureIndex = textureIndex, Opacity = opacity, Size = size, StalagmiteUpsideDown = stalagmiteUpsideDown, OpacityIsTarget = opacityIsTarget, Callback = null }; if (action == ActionType.Smooth || action == ActionType.BETA_Sharpen) { kernelOperation.Params = p; Modify(kernelOperation); } else { basicOperation.Params = p; Modify(basicOperation); } } /// /// Modify the terrain at runtime by performing the operation asynchronously to have less impact on frame rate. /// It is NOT possible to call this method if another asynchronous modification is already in progress. /// Use "IsRunningAsync" property to check if an asynchronous modification is already in progress. /// /// The operation to perform /// Callback method called once modification is done public IEnumerator ModifyAsync(IOperation operation, Action callback = null) where T : struct, IJobParallelFor { if (isRunningAsync) { Debug.LogError("Cannot Modify as asynchronous modification is already in progress"); yield break; } isRunningAsync = true; foreach (var diggerSystem in diggerSystems) { var area = operation.GetAreaToModify(diggerSystem); if (!area.NeedsModification) continue; yield return diggerSystem.ModifyAsync(operation); } foreach (var diggerSystem in diggerSystems) { var area = operation.GetAreaToModify(diggerSystem); if (!area.NeedsModification) continue; diggerSystem.ApplyModify(); } isRunningAsync = false; callback?.Invoke(); } /// /// Modify the terrain at runtime by performing the requested action asynchronously to have less impact on frame rate. /// It is NOT possible to call this method if another asynchronous modification is already in progress. /// Use "IsRunningAsync" property to check if an asynchronous modification is already in progress. /// /// Position where you want to edit the terrain /// Brush type /// Action type /// Index of the texture to be used (starting from 0 to 7). See DiggerMaster inspector to know what texture corresponds to an index. /// Strength/intensity of edit /// Size of edit /// Height of stalagmite (only when Brush is stalagmite) /// Defines if stalagmite is upside-down or not (only when Brush is stalagmite) /// If true when painting texture, the weight of the texture will be directly set to the given opacity /// A callback function that will be called once modification is done public IEnumerator ModifyAsync(Vector3 position, BrushType brush, ActionType action, int textureIndex, float opacity, float size, float stalagmiteHeight = 8f, bool stalagmiteUpsideDown = false, bool opacityIsTarget = false, Action callback = null) { if (action == ActionType.Smooth && brush != BrushType.Sphere) { Debug.LogError("Smooth action only supports Sphere brush"); brush = BrushType.Sphere; } var p = new ModificationParameters { Position = position, Brush = brush, Action = action, TextureIndex = textureIndex, Opacity = opacity, Size = size, StalagmiteUpsideDown = stalagmiteUpsideDown, OpacityIsTarget = opacityIsTarget, Callback = callback }; if (action == ActionType.Smooth || action == ActionType.BETA_Sharpen) { kernelOperation.Params = p; return ModifyAsync(kernelOperation, callback); } basicOperation.Params = p; return ModifyAsync(basicOperation, callback); } /// /// Modification parameters public IEnumerator ModifyAsync(ModificationParameters p) { return ModifyAsync(p.Position, p.Brush, p.Action, p.TextureIndex, p.Opacity, p.Size.x, p.Size.y, p.StalagmiteUpsideDown, p.OpacityIsTarget, p.Callback); } /// /// Modify the terrain at runtime by performing the requested action asynchronously to have less impact on frame rate. /// If another asynchronous modification is already in progress, this modification will be added to a buffer and will be performed later. /// If the buffer is full, the modification is discarded and this method returns false. /// /// True if the modification could be appended to the buffer. False if the buffer is full and modification is discarded. /// Position where you want to edit the terrain /// Brush type /// Action type /// Index of the texture to be used (starting from 0 to 7). See DiggerMaster inspector to know what texture corresponds to an index. /// Strength/intensity of edit /// Size of edit /// Height of stalagmite (only when Brush is stalagmite) /// Defines if stalagmite is upside-down or not (only when Brush is stalagmite) /// If true when painting texture, the weight of the texture will be directly set to the given opacity /// A callback function that will be called once modification is done public bool ModifyAsyncBuffured(Vector3 position, BrushType brush, ActionType action, int textureIndex, float opacity, float size, float stalagmiteHeight = 8f, bool stalagmiteUpsideDown = false, bool opacityIsTarget = false, Action callback = null) { if (buffer.Count >= BufferSize) { return false; } return ModifyAsyncBuffured(new ModificationParameters { Position = position, Brush = brush, Action = action, TextureIndex = textureIndex, Opacity = opacity, Size = size, StalagmiteUpsideDown = stalagmiteUpsideDown, OpacityIsTarget = opacityIsTarget, Callback = callback }); } /// /// Modification parameters public bool ModifyAsyncBuffured(ModificationParameters parameters) { if (buffer.Count >= BufferSize) { return false; } buffer.Enqueue(parameters); return true; } private void Update() { if (!isRunningAsync && buffer.Count > 0) { var parameters = buffer.Dequeue(); StartCoroutine(ModifyAsync(parameters)); } } /// /// Persists all modifications made with Digger since the last persist call. /// public void PersistAll() { if (isRunningAsync) { Debug.LogError("Cannot Persist as asynchronous modification is already in progress"); return; } if (!Application.isEditor) { foreach (var diggerSystem in diggerSystems) { diggerSystem.PersistAtRuntime(); } } else { Debug.Log("Digger 'PersistAll' method has no effect in Unity editor"); } } /// /// Deletes all Digger data that was persisted at runtime. /// public void DeleteAllPersistedData() { if (!Application.isEditor) { foreach (var diggerSystem in diggerSystems) { diggerSystem.DeleteDataPersistedAtRuntime(); } } else { Debug.Log("Digger 'DeleteAllPersistedData' method has no effect in Unity editor"); } } /// /// When a path-prefix is specified, Digger will persist/delete data at "Application.persistentDataPath/DiggerData/pathPrefix/". /// Otherwise it will persist/delete data at "Application.persistentDataPath/DiggerData/" /// /// public void SetPersistenceDataPathPrefix(string pathPrefix) { // we do not use diggerSystems field as it might not be initialized when this method is called foreach (var diggerSystem in FindObjectsOfType()) { diggerSystem.PersistenceSubPath = pathPrefix; } } /// /// Setups Digger on a new terrain that has been created at runtime. /// There MUST be at least one other terrain with Digger already setup at edit-time. /// /// Terrain on which Digger must be added /// Optionally, you can set a specific Digger GUID for this terrain. This is useful if you plan to persist data of terrains created at runtime to be able to load data back on the next launch. public void SetupRuntimeTerrain(Terrain terrain, string guid = null) { var existingDiggerSystem = FindObjectOfType(); if (!existingDiggerSystem) { Debug.LogError( "SetupRuntimeTerrain needs at least one terrain to be already setup with Digger. You must have at least one terrain with Digger on it " + "to be able to setup other terrains at runtime"); return; } var go = new GameObject("Digger"); go.transform.parent = terrain.transform; go.transform.localPosition = Vector3.zero; go.transform.localRotation = Quaternion.identity; go.transform.localScale = Vector3.one; var diggerSystem = go.AddComponent(); diggerSystem.Guid = guid; diggerSystem.PreInit(true); diggerSystem.PersistenceSubPath = existingDiggerSystem.PersistenceSubPath; diggerSystem.Materials = existingDiggerSystem.Materials; diggerSystem.TerrainTextures = existingDiggerSystem.TerrainTextures; diggerSystem.Terrain.terrainData.enableHolesTextureCompression = false; diggerSystem.Terrain.materialTemplate = existingDiggerSystem.Terrain.materialTemplate; diggerSystem.Init(Application.isEditor ? LoadType.Minimal : LoadType.Minimal_and_LoadVoxels); RefreshTerrainList(); } /// /// Refreshes the list of available Digger systems at runtime. /// Call this after you delete a terrain at runtime (for example). /// public void RefreshTerrainList() { diggerSystems = FindObjectsOfType(); } private void Awake() { diggerSystems = FindObjectsOfType(); foreach (var diggerSystem in diggerSystems) { Init(diggerSystem); } } private void Init(DiggerSystem diggerSystem) { if (diggerSystem.IsInitialized) return; diggerSystem.Terrain.terrainData.enableHolesTextureCompression = false; diggerSystem.PreInit(enablePersistence); diggerSystem.Init(Application.isEditor ? LoadType.Minimal : LoadType.Minimal_and_LoadVoxels); } } }