using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
using System.Collections;
using System.Collections.Generic;
using System;
using System.IO;
using System.Text;
using UnityEngine.SocialPlatforms;
using UnityEngine.UI;
#if UNITY_EDITOR && !UNITY_2021_2_OR_NEWER
using UnityEngine.Experimental.TerrainAPI;
#endif
using System.Linq;
namespace Gaia
{
public enum AutoSpawnerStatus { Initial, Queued, Spawning, Done }
///
/// Class to hold data about spawners being automatically triggered by a stamper
///
[System.Serializable]
public class AutoSpawner
{
public bool isActive = true;
public AutoSpawnerStatus status = AutoSpawnerStatus.Initial;
public float lastSpawnTimeStamp = float.MinValue;
public Spawner spawner;
}
///
/// Class to hold data about mask exports being automatically triggered by a stamper
///
[System.Serializable]
public class AutoMaskExport
{
public bool isActive = true;
#if GAIA_PRO_PRESENT
public MaskMapExport maskMapExport;
#endif
}
///
/// Class to apply stamps to the terrain
///
[ExecuteInEditMode]
[System.Serializable]
public class Stamper : MonoBehaviour
{
#region Basic stamp control
///
/// The stamp ID
///
public string m_stampID = Guid.NewGuid().ToString();
///
/// A high quality texture from the stamp raw data - used for the stamper preview
///
public Texture2D m_stampImage;
///
/// The stamp image GUID, used for (re-) loading the stamp when required
///
public string m_stampImageGUID = "";
public bool m_autoSpawnersToggleAll = false;
public bool m_autoMaskExportersToggleAll = false;
public List m_autoSpawners = new List();
public List m_autoMaskExporter = new List();
public List m_lastAffectedTerrains = new List();
public float m_lastActiveTerrainSize;
///
/// Of the resoucces are missing then base sea level off this instead
///
[NonSerialized]
public float m_seaLevel = 0f;
public StamperSettings m_settings;
//public StamperSettings.ClipData[] m_clipData = new StamperSettings.ClipData[0];
#endregion
#region Stamp variables
///
/// Toggling this value will toggle the inversion status on the stamp - preset to have the stamp inverted when it is loaded
///
public bool m_invertStamp = false;
///
/// Toggling this value will toggle the normalisation status of the stamp - preset to have the stamp normalised when loaded
///
public bool m_normaliseStamp = false;
///
/// Stamp operation type - determines how the stamp will be applied
///
//public GaiaConstants.FeatureOperation m_stampOperation = GaiaConstants.FeatureOperation.RaiseHeight;
///
/// Stamp smooth iterations - the number of smoothing iterations to be applied to a stamp before stamping - use to clean up noisy stamps
/// without affecting rest of the terrain.
///
public int m_smoothIterations = 0;
///
/// The physical stencil height in meters - adds or subtracts to that height based on the stamp height 0.. no impact... 1.. full impact.
/// Most normalise stamp first for this to be accurate. Only used for Constants.FeatureOperation.StencilHeight
///
public float m_stencilHeight = 1f;
///
/// A curve that influences adjusts the height of the stamp
///
public AnimationCurve m_heightModifier = AnimationCurve.Linear(0f, 0f, 1f, 1f);
///
/// A curve that influences the strength of the stamp over distance from centre. LHS is centre of stamp, RHS is outer edge of stamp.
///
public AnimationCurve m_distanceMask = new AnimationCurve(new Keyframe(0f, 1f), new Keyframe(1f, 1f));
///
/// Determines whether the distance mask will be applied to the total spawning result, or only to the stamp itself
///
public GaiaConstants.MaskInfluence m_distanceMaskInfluence = GaiaConstants.MaskInfluence.OnlyStampItself;
///
/// A flag to determine if the distance Mask was changed so that the shader input must be reprocessed
///
public bool m_MaskTexturesDirty;
///
/// The area mask to apply
///
public GaiaConstants.ImageFitnessFilterMode m_areaMaskMode = GaiaConstants.ImageFitnessFilterMode.None;
///
/// The source texture used for area based filters, can be used in conjunction with the distance mask. Values range in 0..1.
///
public Texture2D m_imageMask;
///
/// The GUID to the image mask. Used for (re-) loading the image mask via the asset database if required.
///
public string m_imageMaskGUID;
///
/// Determines whether the area mask will be applied to the total spawning result, or only to the stamp itself
///
public GaiaConstants.MaskInfluence m_areaMaskInfluence = GaiaConstants.MaskInfluence.OnlyStampItself;
///
/// This is used to invert the strengh supplied image mask texture
///
public bool m_imageMaskInvert = false;
///
/// This is used to invert the strengh supplied image mask texture
///
public bool m_imageMaskNormalise = false;
///
/// Flip the x, z of the image texture - sometimes required to match source with unity terrain
///
public bool m_imageMaskFlip = false;
///
/// This is used to smooth the supplied image mask texture
///
public int m_imageMaskSmoothIterations = 3;
///
/// The heightmap for the image filter
///
[NonSerialized]
public HeightMap m_imageMaskHM;
///
/// Seed for noise based fractal
///
public float m_noiseMaskSeed = 0;
///
/// The amount of detail in the fractal - more octaves mean more detail and longer calc time.
///
public int m_noiseMaskOctaves = 8;
///
/// The roughness of the fractal noise. Controls how quickly amplitudes diminish for successive octaves. 0..1.
///
public float m_noiseMaskPersistence = 0.25f;
///
/// The frequency of the first octave
///
public float m_noiseMaskFrequency = 1f;
///
/// The frequency multiplier between successive octaves. Experiment between 1.5 - 3.5.
///
public float m_noiseMaskLacunarity = 1.5f;
///
/// The zoom level of the noise
///
public float m_noiseZoom = 10f;
///
/// Used to force the stamp to always show itself when you select something else in the editor
///
public bool m_alwaysShow = false;
///
/// Set this to true if we want to show a box around the stamp to better visualize the position & extents
///
public bool m_showBoundingBox = true;
///
/// Set this to true if we want to show the base
///
public bool m_showBase = true;
///
/// Used to get the stamp to draw the sea level
///
public bool m_showSeaLevelPlane = true;
///
/// Used to show the stamp rulers - some people might find this useful
///
public bool m_showRulers = false;
///
/// Shows the terrain helper - handy helper feature
///
//public bool m_showTerrainHelper = false;
///
/// If the stamper will sync the heightmaps after stamping. Only set to false if you intend to call SyncHeightmaps manually after the stamping!
///
public bool m_syncHeightmaps = true;
///
/// flag to determine whether the stamp preview needs to be re-calculated
///
public bool m_stampDirty = true;
private bool m_lastScaleChangeWasX;
///
/// The associated stamp token, if any. If there is a token associated with this stamper, it will try to synchronize its position on the world map.
///
public WorldMapStampToken m_worldMapStampToken;
#region Effect Properties
///
/// Whether or not the stamper operation will be recorded in the undo stack.
///
public bool m_recordUndo = true;
///
/// The index of the UndoOperation we are currently looking at in the scene view.
///
public int m_currentStamperUndoOperation = 0;
///
/// The recorded undo operations for this stamper.
///
public List m_stamperUndoOperations = new List();
#endregion
///
/// Gizmo colour
///
[NonSerialized]
public Color m_gizmoColour = new Color(1f, .6f, 0f, 1f);
///
/// Use for co-routine simulation
///
[NonSerialized]
public IEnumerator m_updateCoroutine;
///
/// Amount of time per allowed update
///
[NonSerialized]
public float m_updateTimeAllowed = 1f / 30f;
///
/// Current progress on updating the stamp
///
[NonSerialized]
public float m_stampProgress = 0f;
///
/// Whether or not its completed processing
///
[NonSerialized]
public bool m_stampComplete = true;
///
/// Whether or not to cancel it
///
[NonSerialized]
public bool m_cancelStamp = false;
///
/// The material used to preview the stamp
///
[NonSerialized]
public Material m_previewMaterial;
///
/// The biome controller created initially on terrain / world creation with Gaia.
/// If set, the user can select / spawn the biome directly from buttons in the stamper.
///
public BiomeController m_linkedBiomeController;
#endregion
#region Private variables
private Texture2D m_heightTransformCurveTexture;
private Texture2D heightTransformCurveTexture
{
get
{
return ImageProcessing.CreateMaskCurveTexture(ref m_heightTransformCurveTexture);
}
}
///
/// Current feature ID - used to do feature change detection
///
#pragma warning disable 414
private int m_featureID;
#pragma warning restore 414
///
/// Internal variables to control how the stamp stamps
///
private int m_scanWidth = 0;
private int m_scanDepth = 0;
private int m_scanHeight = 0;
private float m_scanResolution = 0.1f; //Every 10 cm
private Bounds m_scanBounds;
private UnityHeightMap m_stampHM;
private GaiaWorldManager m_undoMgr;
private GaiaWorldManager m_redoMgr;
private MeshFilter m_previewFilter;
private MeshRenderer m_previewRenderer = null;
private GaiaMultiTerrainOperation m_worldMapPreviewOperation;
private int m_editorUpdateCount;
private GaiaSettings m_gaiaSettings;
private GaiaSettings GaiaSettings
{
get
{
if (m_gaiaSettings == null)
{
m_gaiaSettings = GaiaUtils.GetGaiaSettings();
}
return m_gaiaSettings;
}
}
//Holds the last valid terrain the stamper was over
//If the stamper was moved out of the bounds of that terrain and no
//other terrain is there to take over, stamp preview will be drawn based on this terrain.
//private Terrain m_lastActiveTerrain;
private Texture2D m_distanceMaskCurveTexture;
private Texture2D distanceMaskCurveTexture
{
get
{
if (m_distanceMaskCurveTexture == null)
{
TextureFormat format = TextureFormat.RGB24;
if (SystemInfo.SupportsTextureFormat(TextureFormat.RFloat))
format = TextureFormat.RFloat;
else if (SystemInfo.SupportsTextureFormat(TextureFormat.RHalf))
format = TextureFormat.RHalf;
m_distanceMaskCurveTexture = new Texture2D(256, 1, format, false, true)
{
name = "Distance mask curve texture",
wrapMode = TextureWrapMode.Clamp,
filterMode = FilterMode.Bilinear,
anisoLevel = 0,
hideFlags = HideFlags.DontSave
};
}
return m_distanceMaskCurveTexture;
}
}
private Texture2D m_transformHeightCurveTexture;
private Texture2D transformHeightCurveTexture
{
get
{
if (m_transformHeightCurveTexture == null)
{
TextureFormat format = TextureFormat.RGB24;
if (SystemInfo.SupportsTextureFormat(TextureFormat.RFloat))
format = TextureFormat.RFloat;
else if (SystemInfo.SupportsTextureFormat(TextureFormat.RHalf))
format = TextureFormat.RHalf;
m_transformHeightCurveTexture = new Texture2D(256, 1, format, false, true)
{
name = "Distance mask curve texture",
wrapMode = TextureWrapMode.Clamp,
filterMode = FilterMode.Bilinear,
anisoLevel = 0,
hideFlags = HideFlags.DontSave
};
}
return m_transformHeightCurveTexture;
}
}
//Eroder class reference for the erosion feature
#if UNITY_EDITOR && GAIA_PRO_PRESENT
private HydraulicEroder m_Eroder = null;
#endif
//holds the material for the current FX used in the stamper
private Material m_currentFXMaterial;
//cached Render Texture for storing the result of stamp processing
//this version is used to display the stamp preview until stamper values change
public RenderTexture m_cachedRenderTexture;
//This is a cached version of the current mask used in the stamper, used for the mask preview
public RenderTexture m_cachedMaskTexture;
//Is the stamp preview enabled or not?
public bool m_drawPreview = true;
//stores the last terrain the stamper was positioned over
//public Terrain m_cachedTerrain;
//should the sea level be shown in the stamp preview?
public bool m_showSeaLevelinStampPreview = true;
public float m_maxCurrentTerrainHeight;
public float m_minCurrentTerrainHeight;
public bool m_activacteLocationSliders = true;
private GaiaSessionManager m_sessionManager;
public bool m_activatePreviewRequested;
public long m_activatePreviewTimeStamp;
public bool m_heightUpdateRequested;
public bool m_autoSpawnRequested;
public bool m_autoMaskExportRequested;
public bool m_autoSpawnStarted;
public long m_lastHeightmapUpdateTimeStamp;
public bool m_openedFromTerrainGenerator;
public bool m_hasStamped;
public bool m_showAutoSpawnersOnEnable;
private GaiaSessionManager SessionManager
{
get
{
if (m_sessionManager == null)
{
m_sessionManager = GaiaSessionManager.GetSessionManager(false);
}
return m_sessionManager;
}
}
public bool m_sessionEditMode;
public GaiaOperation m_sessionEditOperation;
public int m_impostorLoadingRange;
public bool m_highlightLoadingSettings;
public long m_highlightLoadingSettingsStartedTimeStamp;
public float m_baseTerrainMinHeight;
public float m_baseTerrainMaxHeight;
public bool m_firstFrameAfterStampSpawning = false;
public Color m_worldDesignerStampVisColor0 = Color.red;
public Color m_worldDesignerStampVisColor1 = Color.green;
public Color m_worldDesignerStampVisColor2 = Color.blue;
public Color m_worldDesignerStampVisColor3 = Color.cyan;
public Color m_worldDesignerStampVisColor4 = Color.magenta;
public float m_worldDesignerMinHeight;
public float m_worldDesignerMaxHeight;
public bool m_useCustomPreviewBounds = false;
public Bounds m_worldDesignerPreviewBounds;
public WorldDesignerPreviewMode m_worldDesignerPreviewMode;
public int m_worldDesignerPreviewTiles;
public int m_worldDesignerRenderedStamps = 0;
public List m_worldMapStamperSettings = new List();
private Texture2D m_worldDesignerStampVisCurve0;
public Texture2D worldDesignerStampVisCurve0
{
get
{
return ImageProcessing.CreateMaskCurveTexture(ref m_worldDesignerStampVisCurve0);
}
}
private Texture2D m_worldDesignerStampVisCurve1;
public Texture2D worldDesignerStampVisCurve1
{
get
{
return ImageProcessing.CreateMaskCurveTexture(ref m_worldDesignerStampVisCurve1);
}
}
private Texture2D m_worldDesignerStampVisCurve2;
public Texture2D worldDesignerStampVisCurve2
{
get
{
return ImageProcessing.CreateMaskCurveTexture(ref m_worldDesignerStampVisCurve2);
}
}
private Texture2D m_worldDesignerStampVisCurve3;
public Texture2D worldDesignerStampVisCurve3
{
get
{
return ImageProcessing.CreateMaskCurveTexture(ref m_worldDesignerStampVisCurve3);
}
}
private Texture2D m_worldDesignerStampVisCurve4;
public Texture2D worldDesignerStampVisCurve4
{
get
{
return ImageProcessing.CreateMaskCurveTexture(ref m_worldDesignerStampVisCurve4);
}
}
#if GAIA_PRO_PRESENT
public LoadMode m_loadTerrainMode;
private TerrainLoader m_terrainLoader;
public TerrainLoader TerrainLoader
{
get
{
if (m_terrainLoader == null)
{
if (this != null)
{
m_terrainLoader = gameObject.GetComponent();
if (m_terrainLoader == null)
{
m_terrainLoader = gameObject.AddComponent();
m_terrainLoader.hideFlags = HideFlags.HideInInspector;
}
}
}
return m_terrainLoader;
}
}
#endif
#endregion
public delegate void WorldDesignerStampRenderingFinishedCallback();
public event WorldDesignerStampRenderingFinishedCallback OnWorldDesignerStampRenderingFinished;
#if UNITY_EDITOR
[UnityEditor.Callbacks.DidReloadScripts]
private static void OnScriptsReloaded()
{
//mark shader textures as dirty after compiling so they are being rebuilt
//otherwise the stamper has display issues after compilation
foreach (var obj in GameObject.FindObjectsOfType())
{
obj.m_MaskTexturesDirty = true;
}
}
#endif
#region Public API Methods
///
/// Load the currently selected stamp
///
public void LoadStamp()
{
m_featureID = -1;
m_scanBounds = new Bounds(transform.position, Vector3.one * 10f);
string stampImageName = "";
//See if we have something to load
if (m_stampImage == null)
{
Debug.LogWarning("Can't load feature - texture not set");
return;
}
else
{
#if UNITY_EDITOR
m_stampImageGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(m_stampImage));
#endif
stampImageName = m_stampImage.name;
}
// //Get path
// m_featureID = m_stampPreviewImage.GetInstanceID();
// if (!GaiaUtils.CheckValidGaiaStampPath(m_stampPreviewImage))
// {
//#if UNITY_EDITOR
// EditorUtility.DisplayDialog("OOPS!", "The image you have selected is not a valid Stamp preview. You can find your Stamps and their Stamp previews in one of the directories underneath your Gaia\\Stamps directory. \n\nIf you want to turn this image into a Stamp that can be used by the Stamper then please use the Scanner. You can access the Scanner via the utilities section of the Gaia Manager window. You can open Gaia Manager by pressing Ctrl G, or selecting Window -> Gaia -> Gaia Manager.", "OK");
//#else
// Debug.LogError("The file provided is not a valid stamp. You need to drag the stamp preview from one of the directories underneath your Gaia Stamps directory.");
//#endif
// m_featureID = -1;
// m_stampPreviewImage = null;
// return;
// }
//string path = GaiaUtils.GetGaiaStampPath(m_stampImage);
//Load stamp
m_stampHM = new UnityHeightMap(m_stampImage);
if (!m_stampHM.HasData())
{
m_featureID = -1;
m_stampImage = null;
Debug.LogError("Was unable to load " + m_stampImage.name);
return;
}
//Get metadata
//float[] metaData = new float[5];
//Buffer.BlockCopy(m_stampHM.GetMetaData(), 0, metaData, 0, metaData.Length * 4);
//m_scanWidth = (int)metaData[0];
//m_scanDepth = (int)metaData[1];
//m_scanHeight = (int)metaData[2];
//m_scanResolution = metaData[3];
//m_baseLevel = metaData[4];
//m_scanBounds = new Bounds(transform.position, new Vector3(
// (float)m_scanWidth * m_scanResolution * m_width,
// (float)m_scanHeight * m_scanResolution * m_height,
// (float)m_scanDepth * m_scanResolution * m_width));
//Invert
//Note that stamp inversion is handled by inverting the brush strength before passing it into the
//preview / stamp shader - faster this way than converting the heightmap
//if (m_invertStamp)
//{
// m_stampHM.Invert();
//}
//Normalise
if (m_normaliseStamp)
{
m_stampHM.Normalise();
}
//Smooth
if (m_smoothIterations > 0)
{
SmoothStamp();
}
m_stampImage = m_stampHM.ToTexture();
m_stampImage.name = stampImageName;
m_MaskTexturesDirty = true;
//Generate the feature mesh
//GeneratePreviewMesh();
}
///
/// Load the stamp at the image preview path provided
///
/// Path to the image preview
public void LoadStamp(string imagePreviewPath)
{
#if UNITY_EDITOR
m_stampImage = AssetDatabase.LoadAssetAtPath(imagePreviewPath);
m_stampImage.name = Path.GetFileNameWithoutExtension(imagePreviewPath);
m_stampImageGUID = m_stampImage.name;
#endif
LoadStamp();
}
///
/// Bypass the image preview stuff and load the stamp at runtime - the stamp must be located in the resources directory
///
/// Path eg Assets/Resources/Stamps/Mountain1.bytes
public bool LoadRuntimeStamp(TextAsset stamp)
{
//Load stamp
m_stampHM = new UnityHeightMap(stamp);
if (!m_stampHM.HasData())
{
m_featureID = -1;
m_stampImage = null;
Debug.LogError("Was unable to load textasset stamp");
return false;
}
//Get metadata
float[] metaData = new float[5];
Buffer.BlockCopy(m_stampHM.GetMetaData(), 0, metaData, 0, metaData.Length * 4);
m_scanWidth = (int)metaData[0];
m_scanDepth = (int)metaData[1];
m_scanHeight = (int)metaData[2];
m_scanResolution = metaData[3];
m_settings.m_baseLevel = metaData[4];
m_scanBounds = new Bounds(transform.position, new Vector3(
(float)m_scanWidth * m_scanResolution * m_settings.m_width,
(float)m_scanHeight * m_scanResolution * m_settings.m_height,
(float)m_scanDepth * m_scanResolution * m_settings.m_width));
//Invert
if (m_invertStamp)
{
m_stampHM.Invert();
}
//Normalise
if (m_normaliseStamp)
{
m_stampHM.Normalise();
}
//We are good
return true;
}
///
/// Puts the contents of the heightmap into the stamp image
///
public void UpdateStampImageFromHeightmap()
{
m_stampImage = m_stampHM.ToTexture();
}
///
/// Invert the stamp
///
public void InvertStamp()
{
m_stampHM.Invert();
//GeneratePreviewMesh();
}
///
/// Normalise the stamp - makes stamp use full dynamic range - particularly usefule for stencil
///
public void NormaliseStamp()
{
m_stampHM.Normalise();
//GeneratePreviewMesh();
}
///
/// Executes the smoothing algorithm with the number of times stored in m_smoothIterations
///
public void SmoothStamp()
{
m_stampHM.Smooth(m_smoothIterations);
}
public Terrain GetCurrentTerrain()
{
Terrain currentTerrain = Gaia.TerrainHelper.GetTerrain(transform.position);
//Check if the stamper is over a terrain currently
//if not, we will stamp based on the last active terrain we were over
//if that is null either we can't stamp at all
if (currentTerrain)
{
//Update last active terrain with current
//m_lastActiveTerrain = currentTerrain;
m_lastActiveTerrainSize = currentTerrain.terrainData.size.x;
}
//if not, we check if there is any terrain within the bounds of the stamper
if (currentTerrain == null)
{
float width = m_settings.m_width * 2f;
Bounds stamperBounds = new Bounds(transform.position, new Vector3(width, width, width));
foreach (Terrain t in Terrain.activeTerrains)
{
Bounds worldSpaceBounds = t.terrainData.bounds;
worldSpaceBounds.center = new Vector3(worldSpaceBounds.center.x + t.transform.position.x, worldSpaceBounds.center.y + t.transform.position.y, worldSpaceBounds.center.z + t.transform.position.z);
if (worldSpaceBounds.Intersects(stamperBounds))
{
currentTerrain = t;
break;
}
}
}
return currentTerrain;
}
///
/// Stamp the stamp - will kick the co-routine off
///
/// The terrain names valid for applying this stamp. If no list is supplied, all terrains in the stamp area are automatically valid.
public void Stamp(List validTerrainNames = null, List worldMapStamperSettings = null)
{
GaiaStopwatch.StartEvent("Stamping");
Terrain currentTerrain = GetCurrentTerrain();
if (currentTerrain == null)
{
Debug.LogWarning("The stamper could not find a terrain for stamping! Please move the stamper over an active terrain to stamp!" +
"+");
return;
}
GaiaMultiTerrainOperation operation = new GaiaMultiTerrainOperation(currentTerrain, transform, GetStamperRange(currentTerrain), true, validTerrainNames);
operation.m_isWorldMapOperation = m_settings.m_isWorldmapStamper;
if (m_settings.m_isWorldmapStamper)
{
m_worldDesignerPreviewBounds = operation.GetBounds();
}
RenderTexture result = ApplyBrush(operation, worldMapStamperSettings);
operation.SetHeightmap(result);
m_lastAffectedTerrains = operation.affectedHeightmapData;
//Clean up
operation.CloseOperation();
operation = null;
//Force stamp refresh, since the terrain has changed now
m_stampDirty = true;
m_hasStamped = true;
StartEditorUpdates();
//Stitch seams - if the stamper is aligned with terrain borders, visible seams can occur depending on the stamped content
//we want those seams to be stitched automatically
Bounds worldSpaceBounds = TerrainHelper.GetWorldSpaceBounds(currentTerrain);
Bounds stamperBounds = new Bounds(transform.position, new Vector3(m_settings.m_width * currentTerrain.terrainData.size.x / 100f, currentTerrain.terrainData.size.y, m_settings.m_width * currentTerrain.terrainData.size.z / 100f));
//Direct comparison of floats can give wrong results, treat the values as equal if small difference
if (Mathd.Abs(stamperBounds.min.x - worldSpaceBounds.min.x) < 0.001f)
{
//Aligned with the western border
TerrainHelper.TryStitch(currentTerrain, StitchDirection.West);
}
if (Mathd.Abs(stamperBounds.min.z - worldSpaceBounds.min.z) < 0.001f)
{
//Aligned with the southern border
TerrainHelper.TryStitch(currentTerrain, StitchDirection.South);
}
if (Mathd.Abs(stamperBounds.max.x - worldSpaceBounds.max.x) < 0.001f)
{
//Aligned with the eastern border
TerrainHelper.TryStitch(currentTerrain, StitchDirection.East);
}
if (Mathd.Abs(stamperBounds.max.z - worldSpaceBounds.max.z) < 0.001f)
{
//Aligned with the northern border
TerrainHelper.TryStitch(currentTerrain, StitchDirection.North);
}
GaiaStopwatch.EndEvent("Stamping");
}
public void HighlightLoadingSettings()
{
m_highlightLoadingSettings = true;
m_highlightLoadingSettingsStartedTimeStamp = GaiaUtils.GetUnixTimestamp();
}
public float GetStamperRange(Terrain currentTerrain)
{
if (currentTerrain == null)
{
return 100;
}
//limit the width to not overstep the max internal resolution value of the stamper preview
float range = Mathf.RoundToInt(Mathf.Clamp(m_settings.m_width, 1, GetMaxStamperRange(currentTerrain)));
float widthfactor = currentTerrain.terrainData.size.x / 100f;
return range * widthfactor;
}
public float GetMaxStamperRange(Terrain currentTerrain)
{
if (currentTerrain == null)
{
return 100;
}
return (float)11574 / (float)currentTerrain.terrainData.heightmapResolution * 100;
}
public void SyncHeightmaps()
{
foreach (Terrain t in Terrain.activeTerrains)
{
t.terrainData.SyncHeightmap();
}
}
///
/// Cause any active stamp to cancel itself - the tidy up will happen in the enumerator
///
public void CancelStamp()
{
m_cancelStamp = true;
}
///
/// Returns true if we are currently in process of stamping
///
/// True if stamping, false otherwise
public bool IsStamping()
{
return (m_stampComplete != true);
}
///
/// Resets the stamper back to default values
///
public void Reset()
{
if (m_settings == null)
{
m_settings = ScriptableObject.CreateInstance();
}
m_stampID = Guid.NewGuid().ToString();
m_stampImage = null;
m_stampImageGUID = "";
m_settings.m_x = 0f;
m_settings.m_y = 50f;
m_settings.m_z = 0f;
m_settings.m_width = 100f;
m_settings.m_height = 10f;
m_settings.m_rotation = 0f;
m_invertStamp = false;
m_normaliseStamp = false;
m_settings.m_baseLevel = 0f;
m_settings.m_drawStampBase = true;
m_settings.m_adaptiveBase = false;
m_settings.m_operation = GaiaConstants.FeatureOperation.RaiseHeight;
m_smoothIterations = 0;
m_stencilHeight = 1f;
m_heightModifier = AnimationCurve.Linear(0f, 0f, 1f, 1f);
m_distanceMask = new AnimationCurve(new Keyframe(0f, 1f), new Keyframe(1f, 1f));
m_distanceMaskInfluence = GaiaConstants.MaskInfluence.OnlyStampItself;
m_MaskTexturesDirty = true;
m_areaMaskMode = GaiaConstants.ImageFitnessFilterMode.None;
m_imageMask = null;
m_imageMaskGUID = "";
m_areaMaskInfluence = GaiaConstants.MaskInfluence.OnlyStampItself;
m_imageMaskInvert = false;
m_imageMaskNormalise = false;
m_imageMaskFlip = false;
m_imageMaskSmoothIterations = 0;
m_noiseMaskSeed = 0;
m_noiseMaskOctaves = 8;
m_noiseMaskPersistence = 0.25f;
m_noiseMaskFrequency = 1f;
m_noiseMaskLacunarity = 1.5f;
m_noiseZoom = 10f;
m_syncHeightmaps = true;
m_stampDirty = true;
m_settings.m_contrastFeatureSize = 1;
m_settings.m_contrastStrength = 1;
m_settings.m_terraceCount = 10f;
m_settings.m_terraceJitterCount = 0.5f;
m_settings.m_terraceBevelAmountInterior = 0.5f;
m_settings.m_sharpenRidgesMixStrength = 0.5f;
m_settings.m_sharpenRidgesIterations = 16f;
}
///
/// Update the stamp incase of movement etc
///
/// New location
public void UpdateStamp()
{
//Update location
//Vector3Double origin = SessionManager.GetOrigin();
////Stamper settings are stored as absolute value in world space, so we need to deduct the current origin shift
//transform.position = new Vector3Double(m_settings.m_x - origin.x, m_settings.m_y, m_settings.m_z - origin.z);
////Update scales and rotation
transform.localScale = new Vector3(m_settings.m_width, m_settings.m_height, m_settings.m_width);
transform.localRotation = Quaternion.AngleAxis(m_settings.m_rotation, Vector3.up);
////Update bounds
//m_scanBounds.center = transform.position;
//m_scanBounds.size = new Vector3(
// (float)m_scanWidth * m_scanResolution * m_settings.m_width,
// (float)m_scanHeight * m_scanResolution * m_settings.m_height,
// (float)m_scanDepth * m_scanResolution * m_settings.m_width);
//if (m_stampHM != null)
//{
// m_stampHM.SetBoundsWU(m_scanBounds);
//}
//Mark Erosion textures as dirty, since the stamp has been moved
//they need to be re-evaluated
//m_stampDirty = true;
//Set transform changed to false to stop looped updates in OnDrawGizmos
transform.hasChanged = false;
}
///
/// Align the stamp to ground setting - will need to be followed by an UpdateStamp call
///
public void AlignToGround()
{
Terrain currentTerrain = GetCurrentTerrain();
if (currentTerrain != null)
{
transform.position = new Vector3(transform.position.x, currentTerrain.transform.position.y, transform.position.z);
}
else
{
//No terrain, default to 0
transform.position = new Vector3(transform.position.x, 0f, transform.position.z);
}
}
///
/// Loads Settings into the stamper.
///
/// The settings to load
/// Whether the settings should be instantiated as new object on load - if not, this stamper will modify the original scriptable object that was loaded in!
public void LoadSettings(StamperSettings settingsToLoad, bool instantiateNewSettings = true)
{
if (m_settings != null)
{
m_settings.ClearImageMaskTextures();
}
transform.position = new Vector3Double(settingsToLoad.m_x, settingsToLoad.m_y, settingsToLoad.m_z);
transform.rotation = Quaternion.Euler(0f, settingsToLoad.m_rotation, 0f);
transform.localScale = new Vector3(settingsToLoad.m_width, settingsToLoad.m_height, settingsToLoad.m_width);
if (instantiateNewSettings)
{
m_settings = Instantiate(settingsToLoad);
}
else
{
m_settings = settingsToLoad;
}
//Try to look up all collision layer masks by their name where possible - layer orders could be different from when the stamper was saved.
if (m_settings.m_imageMasks != null)
{
foreach (ImageMask imageMask in m_settings.m_imageMasks.Where(x => x.m_operation == ImageMaskOperation.CollisionMask))
{
imageMask.TryRefreshCollisionMask();
}
}
}
///
/// Gte the height range for this stamp
///
/// Base level for this stamp
/// Minimum height
/// Maximum height
/// True if stamp had data, false otherwise
public bool GetHeightRange(ref float baseLevel, ref float minHeight, ref float maxHeight)
{
if (m_stampHM == null || !m_stampHM.HasData())
{
return false;
}
baseLevel = m_settings.m_baseLevel;
m_stampHM.GetHeightRange(ref minHeight, ref maxHeight);
return true;
}
///
/// Position and fit the stamp perfectly to the terrain - will need to be followed by an UpdateStamp call
///
public void FitToTerrain(Terrain t = null)
{
if (t == null)
{
t = Gaia.TerrainHelper.GetTerrain(transform.position, m_settings.m_isWorldmapStamper);
}
if (t == null)
{
t = Gaia.TerrainHelper.GetActiveTerrain();
}
if (t == null)
{
return;
}
Bounds b = new Bounds();
if (Gaia.TerrainHelper.GetTerrainBounds(t, ref b))
{
//m_height = (b.size.y / 100f) * 2f;
//if (m_stampHM != null && m_stampHM.HasData() != false)
//{
// m_width = (b.size.x / (float)m_stampHM.Width()) * 10f;
//}
//else
//{
// m_width = m_height;
//}
//
m_settings.m_width = b.size.x / t.terrainData.size.x * 100f;
m_settings.m_height = 5f;//t.terrainData.size.y / 400f;
transform.localScale = new Vector3(m_settings.m_width, m_settings.m_height, m_settings.m_width);
//
m_settings.m_x = b.center.x;
m_settings.m_y = t.transform.position.y;
m_settings.m_z = b.center.z;
transform.position = new Vector3Double(m_settings.m_x, m_settings.m_y, m_settings.m_z);
transform.rotation = new Quaternion();
m_stampDirty = true;
}
}
///
/// Check if the stamp has been fit to the terrain - ignoring height
///
/// True if its a match
public bool IsFitToTerrain()
{
Terrain t = Gaia.TerrainHelper.GetTerrain(transform.position);
if (t == null)
{
t = Terrain.activeTerrain;
}
if (t == null || m_stampHM == null || m_stampHM.HasData() == false)
{
Debug.LogError("Could not check if fit to terrain - no terrain present");
return false;
}
Bounds b = new Bounds();
if (TerrainHelper.GetTerrainBounds(t, ref b))
{
float width = (b.size.x / (float)m_stampHM.Width()) * 10f;
float x = b.center.x;
float z = b.center.z;
float rotation = 0f;
if (
width != m_settings.m_width ||
x != m_settings.m_x ||
z != m_settings.m_z ||
rotation != m_settings.m_rotation)
{
return false;
}
else
{
return true;
}
}
return false;
}
///
/// Set up this stamper with a default set of masks for base terrain stamping
///
public void SetBaseTerrainStandardMasks()
{
m_settings.m_imageMasks = new ImageMask[]
{
new ImageMask() { m_operation = ImageMaskOperation.NoiseMask},
//new ImageMask() { m_operation = ImageMaskOperation.NoiseMask},
new ImageMask() { m_operation = ImageMaskOperation.DistanceMask}
};
}
///
/// Flatten all active terrains
///
public void FlattenTerrain()
{
//Get an undo buffer
m_undoMgr = new GaiaWorldManager(Terrain.activeTerrains, m_settings.m_isWorldmapStamper);
m_undoMgr.LoadFromWorld();
//Flatten the world
m_redoMgr = new GaiaWorldManager(Terrain.activeTerrains, m_settings.m_isWorldmapStamper);
m_redoMgr.FlattenWorld();
m_redoMgr = null;
}
///
/// Smooth all active terrains
///
public void SmoothTerrain()
{
//Get an undo buffer
m_undoMgr = new GaiaWorldManager(Terrain.activeTerrains, m_settings.m_isWorldmapStamper);
m_undoMgr.LoadFromWorld();
//Flatten the world
m_redoMgr = new GaiaWorldManager(Terrain.activeTerrains, m_settings.m_isWorldmapStamper);
m_redoMgr.SmoothWorld();
m_redoMgr = null;
}
#endregion
#region Preview methods
///
/// Return true if we have a preview we can use
///
/// True if we can preview
public bool CanPreview()
{
return (m_stampImage != null);
}
///
/// Get current preview state
///
/// Current preview state
public bool CurrentPreviewState()
{
if (m_previewRenderer != null)
{
return m_previewRenderer.enabled;
}
return false;
}
///
/// Show the preview if possible
///
public void ShowPreview()
{
if (m_previewRenderer != null)
{
m_previewRenderer.enabled = true;
}
}
///
/// Hide the preview if possible
///
public void HidePreview()
{
if (m_previewRenderer != null)
{
m_previewRenderer.enabled = false;
}
}
///
/// Toggle the preview mesh on and off
///
public void TogglePreview()
{
m_drawPreview = !m_drawPreview;
}
#endregion
#region Undo / Redo methods
///
/// Whether or not we can undo an operation. Due to memory constraints only one level of undo is supported.
///
///// True if we can undo an operation
//public bool CanUndo()
//{
// if (m_undoMgr == null)
// {
// return false;
// }
// return true;
//}
/////
///// Create an undo - creating an undo always destroys the redo if one existed
/////
//public void CreateUndo()
//{
// //Create new undo manager
// m_undoMgr = new GaiaWorldManager(Terrain.activeTerrains);
// m_undoMgr.LoadFromWorld();
// //And destroy the redo manager
// m_redoMgr = null;
//}
/////
///// Undo a previous operation if possible - create redo so we can redo the undo
/////
//public void Undo()
//{
// if (m_undoMgr != null)
// {
// //Update the session
// AddToSession(GaiaOperation.OperationType.StampUndo, "Undoing stamp");
// m_redoMgr = new GaiaWorldManager(Terrain.activeTerrains);
// m_redoMgr.LoadFromWorld();
// m_undoMgr.SaveToWorld(true);
// }
//}
/////
///// True if the previous undo can be redone
/////
///// True if a redo is possible
//public bool CanRedo()
//{
// if (m_redoMgr == null)
// {
// return false;
// }
// return true;
//}
/////
///// Redo a previous operation if possible
/////
//public void Redo()
//{
// if (m_redoMgr != null)
// {
// //Update the session
// AddToSession(GaiaOperation.OperationType.StampRedo, "Redoing stamp");
// m_redoMgr.SaveToWorld(true);
// m_redoMgr = null;
// }
//}
#endregion
#region Unity Related Methods
///
/// Called when the stamp is enabled, loads stamp if necessary
///
void OnEnable()
{
//Check for changed feature and load if necessary
if (m_stampImage != null)
{
LoadStamp();
}
m_stampDirty = true;
#if UNITY_EDITOR && GAIA_PRO_PRESENT
m_Eroder = new HydraulicEroder();
m_Eroder.OnEnable();
#endif
SessionManager.CheckForNewTerrainsForMinMax();
}
public void UpdateTerrainLoader()
{
#if GAIA_PRO_PRESENT
if (m_loadTerrainMode != LoadMode.Disabled)
{
Terrain currentTerrain = GetCurrentTerrain();
float sizeFactor = 1;
if (currentTerrain != null)
{
sizeFactor = currentTerrain.terrainData.size.x / 100f;
}
else
{
if (m_lastActiveTerrainSize > 0)
{
//we have a width from the last active terrain, take this then
sizeFactor = m_lastActiveTerrainSize / 100f;
}
else
{
//no terrain? try placeholders then
TerrainScene terrainScene = Gaia.TerrainHelper.GetDynamicLoadedTerrain(transform.position, SessionManager);
if (terrainScene != null)
{
sizeFactor = (float)terrainScene.m_bounds.size.x / 100f;
}
else
{
//last resort: default to 2048
sizeFactor = 2048 / 100f;
}
}
}
float rad = (transform.rotation.eulerAngles.y + 45) * Mathf.Deg2Rad;
float width = transform.localScale.x * sizeFactor * Mathf.Sqrt(2) * Mathf.Max(Mathf.Abs(Mathf.Cos(rad)), Mathf.Abs(Mathf.Sin(rad)));
//Overextend the height for the terrain loading a bit on top & bottom
//If the stamper only loads to the exact height it can be irritating
//when you adjust the stamper 1mm above the max terrain height & all terrains unload immediately
float height = transform.localScale.y * sizeFactor * 6f;
Vector3 center = transform.position;
TerrainLoader.m_loadingBoundsRegular.center = center;
TerrainLoader.m_loadingBoundsRegular.size = new Vector3(width, height, width);
TerrainLoader.m_loadingBoundsImpostor.center = center;
if (m_impostorLoadingRange > 0)
{
TerrainLoader.m_loadingBoundsImpostor.size = new Vector3(width + m_impostorLoadingRange, height + m_impostorLoadingRange, width + m_impostorLoadingRange);
}
else
{
TerrainLoader.m_loadingBoundsImpostor.size = Vector3.zero;
}
}
//Always set the load mode anew, setting it triggers re-evaluation of the loaded terrains
TerrainLoader.LoadMode = m_loadTerrainMode;
#endif
}
///
/// Updates the minimum and maximum height from the terrain(s). The results are then updated in the image masks of the stamper settings, and all associated spawners are updated as well.
///
/// The stamper settings to store the updated results in.
/// Allows to set a custom fixed min max height instead of actually measuring the terrains
/// The minimum to inject
/// The maximum to inject
public void UpdateMinMaxHeight(StamperSettings stamperSettings = null, bool injectValues = false, float injectMin = 0, float injectMax = 0)
{
if (stamperSettings == null)
{
stamperSettings = m_settings;
}
if (!injectValues)
{
SessionManager.GetWorldMinMax(ref m_minCurrentTerrainHeight, ref m_maxCurrentTerrainHeight, m_settings.m_isWorldmapStamper, this);
}
else
{
m_minCurrentTerrainHeight = injectMin;
m_maxCurrentTerrainHeight = injectMax;
}
//Update the image masks with the newest terrain values after stamping, so that height masks etc. are updated instantly
if (m_settings.m_imageMasks.Length > 0)
{
foreach (ImageMask mask in m_settings.m_imageMasks)
{
mask.m_seaLevel = m_seaLevel;
mask.m_maxWorldHeight = m_maxCurrentTerrainHeight;
mask.m_minWorldHeight = m_minCurrentTerrainHeight;
}
ImageMask.CheckMaskStackForInvalidTextureRules("Stamper", this.name, m_settings.m_imageMasks);
}
//Look for all spawners in the scene and update their heightmaps in case they are over the affected terrain
Spawner[] spawners = FindObjectsOfType();
int length = spawners.Length;
for (int i = 0; i < length; i++)
{
spawners[i].UpdateMinMaxHeight();
}
}
public void FitToAllTerrains(bool loadedOnly = false)
{
BoundsDouble b = new BoundsDouble();
BoundsDouble totalBounds = new BoundsDouble();
TerrainHelper.GetTerrainBounds(ref totalBounds);
if (TerrainHelper.GetTerrainBounds(ref b, loadedOnly))
{
Terrain terrain = GetCurrentTerrain();
m_settings.m_x = (float)b.center.x;
if (terrain != null)
{
m_settings.m_y = terrain.transform.position.y;
m_settings.m_width = (float)b.size.x / terrain.terrainData.size.x * 100f;
m_settings.m_width = Mathf.RoundToInt(Mathf.Clamp(m_settings.m_width, 1, GetMaxStamperRange(terrain)));
}
else
{
#if GAIA_PRO_PRESENT
TerrainScene terrainScene = Gaia.TerrainHelper.GetDynamicLoadedTerrain(transform.position, SessionManager);
//no terrain? assume placeholders then
if (terrainScene != null)
{
m_settings.m_y = 0;
m_settings.m_width = (float)b.size.x / (float)terrainScene.m_bounds.size.x * 100f;
m_settings.m_width = Mathf.RoundToInt(Mathf.Clamp(m_settings.m_width, 1, GetMaxStamperRange(terrain)));
}
else
{
#endif
//no terrain, no placeholder? Default to arbitary values then
m_settings.m_width = 100;
m_settings.m_height = 100;
m_settings.m_y = 0;
#if GAIA_PRO_PRESENT
}
#endif
}
m_settings.m_z = (float)b.center.z;
transform.position = new Vector3Double(m_settings.m_x, m_settings.m_y, m_settings.m_z);
m_settings.m_height = 5 * (float)b.size.x / (float)totalBounds.size.x;
transform.localScale = new Vector3(m_settings.m_width, m_settings.m_height, m_settings.m_width);
transform.rotation = new Quaternion();
m_stampDirty = true;
DrawStampPreview();
}
}
private void OnDisable()
{
DestroyBakedTextures();
ClearRTCache();
}
private void OnDestroy()
{
DestroyBakedTextures();
ClearRTCache();
#if UNITY_EDITOR && GAIA_PRO_PRESENT
if (m_Eroder != null)
{
m_Eroder.ReleaseRenderTextures();
m_Eroder = null;
}
#endif
if (m_settings != null)
{
m_settings.ClearImageMaskTextures();
}
}
private void ClearRTCache()
{
if (m_cachedRenderTexture != null)
{
m_cachedRenderTexture.Release();
m_cachedRenderTexture = null;
}
}
private void DestroyBakedTextures()
{
//Clean up baked Texture2Ds
if (m_distanceMaskCurveTexture != null)
DestroyImmediate(m_distanceMaskCurveTexture);
m_distanceMaskCurveTexture = null;
if (m_transformHeightCurveTexture != null)
DestroyImmediate(m_transformHeightCurveTexture);
m_transformHeightCurveTexture = null;
}
///
/// Called when app starts
///
void Start()
{
//Hide stamp preview mesh at runtime
if (Application.isPlaying)
{
HidePreview();
}
}
///
/// Start editor updates
///
public void StartEditorUpdates()
{
#if UNITY_EDITOR
EditorApplication.update += EditorUpdate;
#endif
}
//Stop editor updates
public void StopEditorUpdates()
{
#if UNITY_EDITOR
EditorApplication.update -= EditorUpdate;
#endif
}
///
/// This is executed only in the editor - using it to simulate co-routine execution and update execution
///
void EditorUpdate()
{
#if UNITY_EDITOR
if (m_updateCoroutine != null)
{
if (m_editorUpdateCount > 50)
{
try
{
m_updateCoroutine.MoveNext();
m_editorUpdateCount = 0;
}
catch (Exception ex)
{
Debug.LogError($"Error during the execution of a co-routine. Co-Routine: {m_updateCoroutine.ToString()}, Exception: {ex.Message}, Stack Tace:{ex.StackTrace}");
m_updateCoroutine = null;
ProgressBar.Clear(ProgressBarPriority.Maintenance);
}
}
else
{
m_editorUpdateCount++;
}
}
else
{
if (m_lastHeightmapUpdateTimeStamp + 10 < GaiaUtils.GetUnixTimestamp())
{
if (!SessionManager.m_worldCreationRunning && SessionManager.m_session.m_operations.Exists(x => x.sessionPlaybackState == SessionPlaybackState.Queued))
{
GaiaSessionManager.ContinueSessionPlayback();
}
else
{
//If nothing to wait for or to continue in the session -> destroy the temporary tools, if any
if (!SessionManager.m_worldCreationRunning && (SessionManager.m_massStamperSettingsList == null || SessionManager.m_massStamperSettingsIndex >= SessionManager.m_massStamperSettingsList.Count - 1))
{
GaiaSessionManager.AfterSessionPlaybackCleanup();
}
}
StopEditorUpdates();
}
}
#endif
}
public void DrawStampPreview(List worldMapStamperSettings = null, bool isWorldMapPreview = false)
{
if (!m_drawPreview)
{
return;
}
//We should not start drawing a stamp preview while a coroutine is running
if (m_updateCoroutine != null)
{
return;
}
if (transform.position.x != m_settings.m_x)
{
m_settings.m_x = transform.position.x;
m_stampDirty = true;
}
if (transform.position.y != m_settings.m_y)
{
m_settings.m_y = transform.position.y;
m_stampDirty = true;
}
if (transform.position.z != m_settings.m_z)
{
m_settings.m_z = transform.position.z;
m_stampDirty = true;
}
if (m_lastScaleChangeWasX)
{
if (transform.localScale.x != m_settings.m_width)
{
m_settings.m_width = transform.localScale.x;
m_stampDirty = true;
m_lastScaleChangeWasX = true;
}
else
{
if (transform.localScale.z != m_settings.m_width)
{
m_settings.m_width = transform.localScale.z;
m_stampDirty = true;
m_lastScaleChangeWasX = false;
}
}
}
else
{
if (transform.localScale.z != m_settings.m_width)
{
m_settings.m_width = transform.localScale.z;
m_stampDirty = true;
m_lastScaleChangeWasX = false;
}
else
{
if (transform.localScale.x != m_settings.m_width)
{
m_settings.m_width = transform.localScale.x;
m_stampDirty = true;
m_lastScaleChangeWasX = true;
}
}
}
if (transform.rotation.eulerAngles.y != m_settings.m_rotation)
{
m_settings.m_rotation = transform.rotation.eulerAngles.y;
m_stampDirty = true;
}
if (transform.localScale.y != m_settings.m_height)
{
if (transform.localScale.y > 0f)
{
m_settings.m_height = transform.localScale.y;
m_stampDirty = true;
}
}
UpdateStamp();
Terrain currentTerrain = GetCurrentTerrain();
if (currentTerrain == null && !m_settings.m_isWorldmapStamper)
{
return;
}
Material material = GaiaMultiTerrainOperation.GetDefaultGaiaStamperPreviewMaterial();
//Switch zTest mode according to operation mode
if (!m_settings.m_isWorldmapStamper)
{
switch (m_settings.m_operation)
{
case GaiaConstants.FeatureOperation.LowerHeight:
material.SetInt("_zTestMode", (int)UnityEngine.Rendering.CompareFunction.Always);
break;
case GaiaConstants.FeatureOperation.SetHeight:
material.SetInt("_zTestMode", (int)UnityEngine.Rendering.CompareFunction.Always);
break;
case GaiaConstants.FeatureOperation.BlendHeight:
material.SetInt("_zTestMode", (int)UnityEngine.Rendering.CompareFunction.Always);
break;
case GaiaConstants.FeatureOperation.SubtractHeight:
material.SetInt("_zTestMode", (int)UnityEngine.Rendering.CompareFunction.Always);
break;
case GaiaConstants.FeatureOperation.HydraulicErosion:
material.SetInt("_zTestMode", (int)UnityEngine.Rendering.CompareFunction.Always);
break;
case GaiaConstants.FeatureOperation.Contrast:
material.SetInt("_zTestMode", (int)UnityEngine.Rendering.CompareFunction.Always);
break;
case GaiaConstants.FeatureOperation.Terrace:
material.SetInt("_zTestMode", (int)UnityEngine.Rendering.CompareFunction.Always);
break;
case GaiaConstants.FeatureOperation.SharpenRidges:
material.SetInt("_zTestMode", (int)UnityEngine.Rendering.CompareFunction.Always);
break;
case GaiaConstants.FeatureOperation.HeightTransform:
material.SetInt("_zTestMode", (int)UnityEngine.Rendering.CompareFunction.Always);
break;
case GaiaConstants.FeatureOperation.PowerOf:
material.SetInt("_zTestMode", (int)UnityEngine.Rendering.CompareFunction.Always);
break;
case GaiaConstants.FeatureOperation.Smooth:
material.SetInt("_zTestMode", (int)UnityEngine.Rendering.CompareFunction.Always);
break;
case GaiaConstants.FeatureOperation.MixHeight:
material.SetInt("_zTestMode", (int)UnityEngine.Rendering.CompareFunction.Always);
break;
default:
material.SetInt("_zTestMode", (int)UnityEngine.Rendering.CompareFunction.LessEqual);
break;
}
}
else
{
//World designer mixes all kinds of operation, "Always" is the better choice here.
material.SetInt("_zTestMode", (int)UnityEngine.Rendering.CompareFunction.Always);
}
// draw result preview
GaiaMultiTerrainOperation operation = new GaiaMultiTerrainOperation(currentTerrain, transform, GetStamperRange(currentTerrain));
operation.m_isWorldMapOperation = m_settings.m_isWorldmapStamper;
//if we are about to draw a world map preview with spawned stamps for the first time, do not draw the first frame
if (m_stampDirty && worldMapStamperSettings != null && worldMapStamperSettings.Count > 0)
{
m_firstFrameAfterStampSpawning = true;
}
RenderTexture preview = ApplyBrush(operation, worldMapStamperSettings, isWorldMapPreview);
material.SetTexture("_HeightmapOrig", operation.RTheightmap);
material.SetColor("_positiveHeightColor", GaiaSettings.m_stamperPositiveHeightChangeColor);
material.SetColor("_negativeHeightColor", GaiaSettings.m_stamperNegativeHeightChangeColor);
if (isWorldMapPreview)
{
//UpdateMinMaxHeight();
//needed for the stamp visualization without stamps being spawned
UpdateMinMaxHeight();
material.SetFloat("_worldDesignerMinHeight", m_minCurrentTerrainHeight);
material.SetFloat("_worldDesignerMaxHeight", m_maxCurrentTerrainHeight);
material.SetColor("_worldDesignerStampVisColor0", m_worldDesignerStampVisColor0);
material.SetColor("_worldDesignerStampVisColor1", m_worldDesignerStampVisColor1);
material.SetColor("_worldDesignerStampVisColor2", m_worldDesignerStampVisColor2);
material.SetColor("_worldDesignerStampVisColor3", m_worldDesignerStampVisColor3);
material.SetColor("_worldDesignerStampVisColor4", m_worldDesignerStampVisColor4);
material.SetTexture("_worldDesignerStampVisCurveTexture0", worldDesignerStampVisCurve0);
material.SetTexture("_worldDesignerStampVisCurveTexture1", worldDesignerStampVisCurve1);
material.SetTexture("_worldDesignerStampVisCurveTexture2", worldDesignerStampVisCurve2);
material.SetTexture("_worldDesignerStampVisCurveTexture3", worldDesignerStampVisCurve3);
material.SetTexture("_worldDesignerStampVisCurveTexture3", worldDesignerStampVisCurve4);
}
else
{
material.SetFloat("_worldDesignerMinHeight", 0.0f);
material.SetFloat("_worldDesignerMaxHeight", 0.0f);
}
//Make the sea level color fully transparent if sea level is deactivated
Color seaLevelColor = GaiaSettings.m_stamperSeaLevelTintColor;
if (!m_showSeaLevelinStampPreview)
{
seaLevelColor.a = 0f;
}
material.SetColor("_seaLevelTintColor", seaLevelColor);
material.SetFloat("_normalMapColorPower", GaiaSettings.m_stamperNormalMapColorPower);
//float relativeSeaLevel = Mathf.Lerp(-0.5f, 0.5f, Mathf.InverseLerp(currentTerrain.transform.position.y - currentTerrain.terrainData.size.y, currentTerrain.transform.position.y + currentTerrain.terrainData.size.y, m_seaLevel));
material.SetFloat("_seaLevel", m_seaLevel);
if (m_worldDesignerPreviewMode == WorldDesignerPreviewMode.Worldmap && m_useCustomPreviewBounds && m_worldMapStamperSettings.Count > 0)
{
material.SetInt("_usePartialPreview", 1);
Bounds opBounds = operation.GetBounds();
float partialPreviewCenterX = Mathf.InverseLerp(opBounds.min.x, opBounds.max.x, m_worldDesignerPreviewBounds.center.x);
float partialPreviewCenterY = Mathf.InverseLerp(opBounds.min.z, opBounds.max.z, m_worldDesignerPreviewBounds.center.z);
float partialPreviewRadius = Mathf.InverseLerp(0, opBounds.size.x, m_worldDesignerPreviewBounds.size.x);
material.SetVector("_partialPreviewCenter",new Vector4(partialPreviewCenterX,partialPreviewCenterY,0f,0f));
material.SetFloat("_partialPreviewRadius", partialPreviewRadius);
}
else
{
material.SetInt("_usePartialPreview", 0);
}
//workaround for stamp spawning - skip the first frame after stamps have been spawned to avoid flickering issues
if (m_firstFrameAfterStampSpawning)
{
m_firstFrameAfterStampSpawning = false;
if (m_updateCoroutine == null)
{
operation.CloseOperation();
DrawStampPreview(null, true);
}
}
else
{
operation.Visualize(MultiTerrainOperationType.Heightmap, preview, material, 1);
//Clean up - but not if we started an editor coroutine while rendering the stamps. In that case we would still need the operation!
if (m_updateCoroutine == null)
{
operation.CloseOperation();
}
}
//GaiaUtils.ReleaseAllTempRenderTextures();
}
public void SetStampScaleByMeter(float absoluteHeightValue)
{
Terrain currentTerrain = GetCurrentTerrain();
if (currentTerrain == null)
{
return;
}
transform.position = new Vector3(transform.position.x, currentTerrain.transform.position.y, transform.position.z);
transform.localScale = new Vector3(transform.localScale.x, m_settings.GetStampScaleByMeter(currentTerrain, absoluteHeightValue), transform.localScale.z);
}
public float CurrentStampScaleToMeter()
{
Terrain currentTerrain = GetCurrentTerrain();
if (currentTerrain == null)
{
return 0;
}
float heightDifference = 0f;
if (m_settings.m_operation == GaiaConstants.FeatureOperation.SubtractHeight)
{
heightDifference = currentTerrain.transform.position.y - transform.position.y;
return Mathf.Lerp(0, -currentTerrain.terrainData.size.y, Mathf.InverseLerp(0, 50f, transform.localScale.y) + Mathf.InverseLerp(0, currentTerrain.terrainData.size.y, heightDifference));
}
else
{
heightDifference = transform.position.y - currentTerrain.transform.position.y;
return Mathf.Lerp(0, currentTerrain.terrainData.size.y, Mathf.InverseLerp(0, 50f, transform.localScale.y) + Mathf.InverseLerp(0, currentTerrain.terrainData.size.y, heightDifference));
}
}
///
/// Draw gizmos when selected
///
void OnDrawGizmosSelected()
{
#if UNITY_EDITOR
if (Selection.activeObject == this.gameObject)
{
DrawGizmos(true);
}
#endif
}
///
/// Draw gizmos when not selected
///
void OnDrawGizmos()
{
//DrawGizmos(false);
}
///
/// Draw the gizmos
///
///
void DrawGizmos(bool isSelected)
{
Terrain activeTerrain = TerrainHelper.GetTerrain(transform.position, m_settings.m_isWorldmapStamper);
if (activeTerrain == null)
{
return;
}
//Determine whether to drop out
if (!isSelected && !m_alwaysShow)
{
return;
}
//Now draw the gizmos
//Stamp Bounding Box
if (m_showBoundingBox)
{
Bounds bounds = new Bounds();
Gizmos.color = new Color(Color.cyan.r, Color.cyan.g, Color.cyan.b, Color.cyan.a / 2f);
float yPos = activeTerrain.transform.position.y + (activeTerrain.terrainData.size.y / 2f);
//The final gizmo will be drawn in LOCAL SPACE of the stamp to inherit its rotation
//we therefore need to offset the scale and ypos of the stamp itself
//offset ypos of the stamper
yPos -= transform.position.y - activeTerrain.transform.position.y;
//offset stamper height scale
yPos /= transform.localScale.y;
bounds.center = new Vector3(0f, yPos, 0f);
//size will be scaled automatically due to stamp scaling
bounds.size = new Vector3(activeTerrain.terrainData.size.x / 100f, activeTerrain.terrainData.size.y / transform.localScale.y, activeTerrain.terrainData.size.x / 100f);
Matrix4x4 currMatrixBoundingBox = Gizmos.matrix;
Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.DrawWireCube(bounds.center, bounds.size);
Gizmos.matrix = currMatrixBoundingBox;
}
//Base Level - only draw when enabled & larger than 0
if (m_showBase && OperationSupportsBaseSettings())
{
//Displaying the base level can be irrelevant when it is all at the bottom for a raise height operation, and vice versa for lowering height.
//In this case we can just hide it, as it just would add visual clutter.
bool relevant = true;
if (m_settings.m_operation == GaiaConstants.FeatureOperation.RaiseHeight || m_settings.m_operation == GaiaConstants.FeatureOperation.AddHeight)
{
if (m_settings.m_baseLevel == 0)
{
relevant = false;
}
}
if (m_settings.m_operation == GaiaConstants.FeatureOperation.LowerHeight || m_settings.m_operation == GaiaConstants.FeatureOperation.SubtractHeight)
{
if (m_settings.m_baseLevel == 1)
{
relevant = false;
}
}
if (relevant)
{
Bounds bounds = new Bounds();
Gizmos.color = new Color(Color.red.r, Color.red.g, Color.red.b, Color.red.a / 2f);
//yPos in world space:
float yPos = Mathf.Lerp(activeTerrain.transform.position.y, activeTerrain.transform.position.y + activeTerrain.terrainData.size.y, m_settings.m_baseLevel);
//The final gizmo will be drawn in LOCAL SPACE of the stamp to inherit its rotation
//we therefore need to offset the scale and ypos of the stamp itself
//offset ypos of the stamper
yPos -= transform.position.y - activeTerrain.transform.position.y;
//offset stamper height scale
yPos /= m_settings.m_height;
bounds.center = new Vector3(0, yPos, 0);
//size will be scaled automatically due to stamp scaling
bounds.size = new Vector3(activeTerrain.terrainData.size.x / 90f, 0.05f, activeTerrain.terrainData.size.x / 90f);
Matrix4x4 currMatrixBaseLevel = Gizmos.matrix;
Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.DrawCube(bounds.center, bounds.size);
Gizmos.matrix = currMatrixBaseLevel;
//if (TerrainHelper.GetTerrainBounds(ref bounds) == true)
//{
// //bounds.center = new Vector3(bounds.center.x, m_scanBounds.min.y + (m_scanBounds.size.y * m_baseLevel), bounds.center.z);
// //bounds.size = new Vector3(bounds.size.x, 0.05f, bounds.size.z);
// //Gizmos.color = new Color(Color.yellow.r, Color.yellow.g, Color.yellow.b, Color.yellow.a / 2f);
// //Gizmos.DrawWireCube(bounds.center, bounds.size);
//}
}
}
//Water
//Check if sea level changed
if (m_seaLevel != SessionManager.GetSeaLevel())
{
m_seaLevel = SessionManager.GetSeaLevel();
m_stampDirty = true;
}
if (m_showSeaLevelPlane && PWS_WaterSystem.Instance == null && !SessionManager.m_worldCreationRunning)
{
BoundsDouble bounds = new BoundsDouble();
if (m_settings.m_isWorldmapStamper)
{
bounds.center = activeTerrain.terrainData.bounds.center;
bounds.extents = activeTerrain.terrainData.bounds.extents;
//bounds need to be in world space + use the shifted origin
bounds.center = transform.position;
}
else
{
TerrainHelper.GetTerrainBounds(ref bounds);
bounds.center = new Vector3Double(bounds.center.x, m_seaLevel, bounds.center.z);
bounds.size = new Vector3Double(bounds.size.x, 0.05f, bounds.size.z);
}
if (isSelected)
{
Gizmos.color = new Color(Color.blue.r, Color.blue.g, Color.blue.b, Color.blue.a / 4f);
Gizmos.DrawCube(bounds.center, bounds.size);
}
else
{
Gizmos.color = new Color(Color.blue.r, Color.blue.g, Color.blue.b, Color.blue.a / 4f);
Gizmos.DrawCube(bounds.center, bounds.size);
}
}
//Stamp box
//Gizmos.color = Color.magenta;
//Gizmos.DrawWireCube(new Vector3(m_x, 0f, m_z), new Vector3(m_width, m_height, m_width));
//Rulers
if (m_showRulers)
{
DrawRulers();
}
//Rotation n size
Matrix4x4 currMatrix = Gizmos.matrix;
Gizmos.matrix = transform.localToWorldMatrix;
Vector3 origSize = new Vector3(
(float)m_scanWidth * m_scanResolution,
(float)m_scanHeight * m_scanResolution,
(float)m_scanDepth * m_scanResolution);
Gizmos.color = new Color(m_gizmoColour.r, m_gizmoColour.g, m_gizmoColour.b, m_gizmoColour.a / 2f);
Gizmos.DrawWireCube(Vector3.zero, origSize);
Gizmos.matrix = currMatrix;
//Terrain bounds
if (activeTerrain != null)
{
Gizmos.color = Color.white;
Bounds b = new Bounds();
Gaia.TerrainHelper.GetTerrainBounds(activeTerrain, ref b);
Gizmos.DrawWireCube(b.center, b.size);
}
}
//private List GetLocalMapNames()
//{
// if (GaiaUtils.HasDynamicLoadedTerrains())
// {
// return Resources.FindObjectsOfTypeAll().Select(x => x.name.Replace("Placeholder ", "")).ToList();
// }
// else
// {
// List returnList = new List();
// foreach (Terrain t in Terrain.activeTerrains)
// {
// if (t != m_worldMap)
// {
// returnList.Add(t.name);
// }
// }
// return returnList;
// }
//}
//private void SyncTerrainHeightmaps(List sourceTerrains, List targetTerrains)
//{
// if (sourceTerrains == null || targetTerrains == null)
// {
// return;
// }
// int sourcePixels = GetTotalHeightmapResolutionPixels(sourceTerrains);
// int targetPixels = GetTotalHeightmapResolutionPixels(targetTerrains);
//}
//private int GetTotalHeightmapResolutionPixels(List terrainNames)
//{
// if (terrainNames == null)
// {
// return 0;
// }
// Bounds bounds = new Bounds();
// bool hasPlaceholders = GaiaUtils.HasDynamicLoadedTerrains();
// foreach (string name in terrainNames)
// {
// foreach (Terrain t in Terrain.activeTerrains)
// {
// if (t.name == name)
// {
// bounds.Encapsulate(t.terrainData.bounds);
// continue;
// }
// }
// //not found in active terrains? need to try to find the placeholder, load the terrain in and encapsulate
// if (hasPlaceholders)
// {
// }
// }
//}
///
/// Returns true if the current operation supports base settings (for base stamping)
///
///
private bool OperationSupportsBaseSettings()
{
return m_settings.m_operation == GaiaConstants.FeatureOperation.AddHeight ||
m_settings.m_operation == GaiaConstants.FeatureOperation.SubtractHeight ||
m_settings.m_operation == GaiaConstants.FeatureOperation.RaiseHeight ||
m_settings.m_operation == GaiaConstants.FeatureOperation.LowerHeight;
}
///
/// Draw the rulers
///
void DrawRulers()
{
#if UNITY_EDITOR
if (m_showRulers)
{
Gizmos.color = Color.green;
//Ruler gizmos
int ticks;
float tickOffset;
float tickInterval = 100f;
float vertRulerSize = m_scanBounds.max.y - m_scanBounds.min.y;
float horizRulerSize = m_scanBounds.max.x - m_scanBounds.min.x;
Vector3 startPosition;
Vector3 endPosition;
Vector3 labelPosition;
//Vertical ruler
startPosition = m_scanBounds.center;
startPosition.y = m_scanBounds.min.y;
endPosition = m_scanBounds.center;
endPosition.y = m_scanBounds.max.y;
labelPosition = startPosition;
labelPosition.x += 5f;
labelPosition.y += 2f;
Gizmos.DrawLine(startPosition, endPosition);
ticks = Mathf.RoundToInt(vertRulerSize / tickInterval);
tickOffset = vertRulerSize / (float)ticks;
for (int i = 0; i <= ticks; i++)
{
Handles.Label(labelPosition, string.Format("{0:0m}", labelPosition.y));
labelPosition.y += tickOffset;
}
//Horizontal ruler - x axis
startPosition = m_scanBounds.center;
startPosition.x = m_scanBounds.min.x;
endPosition = m_scanBounds.center;
endPosition.x = m_scanBounds.max.x;
labelPosition = startPosition;
labelPosition.x += 5f;
labelPosition.y += 2f;
Gizmos.DrawLine(startPosition, endPosition);
ticks = Mathf.RoundToInt(horizRulerSize / tickInterval);
tickOffset = horizRulerSize / (float)ticks;
for (int i = 0; i <= ticks; i++)
{
Handles.Label(labelPosition, string.Format("{0:0m}", labelPosition.x));
labelPosition.x += tickOffset;
}
}
#endif
}
#endregion
#region Private worker methods
///
/// Applies the current image masks and creates a render texture containing the result
///
/// A rendertexture with the mask results/returns>
public RenderTexture ApplyBrush(GaiaMultiTerrainOperation operation, List worldMapStamperSettings = null, bool isWorldMapPreview = false)
{
//Try to get the current terrain the stamper is over, if there is no terrain below, let's take the last cached one instead for our calculations.
Terrain currentTerrain = Gaia.TerrainHelper.GetTerrain(transform.position, m_settings.m_isWorldmapStamper);
//Still no terrain? -> Abort
if (currentTerrain == null && !m_settings.m_isWorldmapStamper)
{
Debug.LogWarning("The Gaia Stamper could find no terrain to work on - Please position the stamper above an active terrain!");
return null;
}
if (isWorldMapPreview)
{
operation.GetHeightmapForWorldMapPreview(transform, TerrainLoaderManager.Instance.TerrainSceneStorage.m_worldMapPreviewRange, TerrainLoaderManager.Instance.TerrainSceneStorage.m_worldMapPreviewHeightmapResolution);
}
else
{
operation.GetHeightmap();
}
m_settings.m_stamperInputImageMask.m_multiTerrainOperation = operation;
if (m_stampDirty)
{
if (m_settings.m_isWorldmapStamper)
{
//For the world map stamper we need to reset min max height to 0, as we start out with a flat terrain
UpdateMinMaxHeight(null, true, 0f, 0f);
}
else
{
UpdateMinMaxHeight();
}
if (!m_settings.m_isWorldmapStamper)
{
operation.GetNormalmap();
operation.CollectTerrainBakedMasks();
}
else
{
if (m_worldDesignerPreviewMode == WorldDesignerPreviewMode.SingleTerrain)
{
//we need to adjust the border image masks scales for displaying a single terrain
foreach (ImageMask mask in m_settings.m_imageMasks)
{
mask.m_imageMaskSpace = ImageMaskSpace.World;
if (mask.m_operation != ImageMaskOperation.ImageMask)
{
mask.m_xOffSet = 0;
mask.m_zOffSet = 0;
mask.m_xScale = m_worldDesignerPreviewTiles;
mask.m_zScale = m_worldDesignerPreviewTiles;
}
else
{
//Do not mess with the offset in case of an image mask, but adjust the scale according to the
//zoom factor, since the user might have entered their own scaling for image input which we need to respect
mask.m_xScale *= m_worldDesignerPreviewTiles;
mask.m_zScale *= m_worldDesignerPreviewTiles;
}
}
}
else
{
//Previewing the entire world, mask scale = 1
foreach (ImageMask mask in m_settings.m_imageMasks)
{
mask.m_imageMaskSpace = ImageMaskSpace.World;
if (mask.m_operation != ImageMaskOperation.ImageMask)
{
mask.m_xOffSet = 0;
mask.m_zOffSet = 0;
mask.m_xScale = 1;
mask.m_zScale = 1;
}
}
}
}
RenderTexture returnRT = ApplyBrushInternal(operation, m_settings);
if (m_settings.m_isWorldmapStamper)
{
if (worldMapStamperSettings != null && worldMapStamperSettings.Count > 0)
{
//now that the base map has been applied, update min and max and keep those during the application of the worldmap stamper settings.
//But only do this for the PREVIEW, the actual stamping on export has the correct min max height stored in the base min max.
if (m_worldDesignerPreviewMode == WorldDesignerPreviewMode.Worldmap)
{
SessionManager.DirtyWorldMapMinMax();
}
UpdateMinMaxHeight();
m_baseTerrainMinHeight = m_minCurrentTerrainHeight;
m_baseTerrainMaxHeight = m_maxCurrentTerrainHeight;
float originalHeight = transform.position.y;
m_worldDesignerRenderedStamps = 0;
m_worldMapStamperSettings = worldMapStamperSettings;
if (!m_useCustomPreviewBounds)
{
m_worldDesignerPreviewBounds = operation.GetBounds();
}
if (m_worldMapStamperSettings.Count > 100 && m_worldDesignerPreviewMode == WorldDesignerPreviewMode.Worldmap)
{
//if we have too many stamps and the preview is not restricted to custom bounds, we need to render on the stamps in an editor coroutine
m_updateCoroutine = RenderStampsCoroutine();
m_worldMapPreviewOperation = operation;
OnWorldDesignerStampRenderingFinished -= OnWDStampRenderFinished;
OnWorldDesignerStampRenderingFinished += OnWDStampRenderFinished;
StartEditorUpdates();
}
else
{
try
{
bool cancel = ProgressBar.Show(ProgressBarPriority.TerrainLoading, "Updating Preview", "Rendering Stamps into the preview...", 1, 2, false, false);
for (int i = 0; i < m_worldMapStamperSettings.Count; i++)
{
StamperSettings stamperSettings = worldMapStamperSettings[i];
//StamperSettings tempCopy = ScriptableObject.Instantiate(stamperSettings);
if (!IsStamperSettingsRelevantForPreview(stamperSettings))
{
continue;
}
StamperSettings tempCopy = PrepareWDStamperSettingsForRendering(stamperSettings);
returnRT = ApplyBrushInternal(operation, tempCopy, true);
m_worldDesignerRenderedStamps++;
//tempCopy.ClearImageMaskTextures();
}
}
catch (Exception ex)
{
Debug.LogError($"Error while rendering a world designer preview. Error Message: {ex.Message} Stack Trace: {ex.StackTrace}");
}
finally
{
ProgressBar.Clear(ProgressBarPriority.TerrainLoading);
transform.position = new Vector3(transform.position.x, originalHeight, transform.position.z);
if (OnWorldDesignerStampRenderingFinished != null)
{
OnWorldDesignerStampRenderingFinished();
}
}
}
}
else
{
//no stamps to render -> finished
if (OnWorldDesignerStampRenderingFinished != null)
{
OnWorldDesignerStampRenderingFinished();
}
}
#if UNITY_EDITOR
SceneView.RepaintAll();
#endif
}
//if (m_worldMapStampToken != null)
//{
// if (!m_settings.m_isWorldmapStamper && m_worldMapStampToken.m_previewOnWorldMap)
// {
// m_worldMapStampToken.ReloadWorldStamper();
// }
//}
GaiaUtils.ReleaseAllTempRenderTextures();
return returnRT;
}
else
{
return m_cachedRenderTexture;
}
}
///
/// Returnst true if the stamper settings are relevant for the current bounds setting of the world designer preview
///
///
///
private bool IsStamperSettingsRelevantForPreview(StamperSettings stamperSettings)
{
//Take rotation into account for the bounds calculation
Vector3 position = new Vector3((float)stamperSettings.m_x, (float)stamperSettings.m_y, (float)stamperSettings.m_z);
float range = 1024;
if (m_worldDesignerPreviewMode == WorldDesignerPreviewMode.Worldmap)
{
range = stamperSettings.m_width / 100f * TerrainLoaderManager.Instance.TerrainSceneStorage.m_worldMapPreviewRange;
}
else
{
range = (stamperSettings.m_width * m_worldDesignerPreviewTiles) / 100f * TerrainLoaderManager.Instance.TerrainSceneStorage.m_worldMapPreviewRange;
}
float rad = (stamperSettings.m_rotation + 45) * Mathf.Deg2Rad;
float rotatedRange = range * Mathf.Sqrt(2) * Mathf.Max(Mathf.Abs(Mathf.Cos(rad)), Mathf.Abs(Mathf.Sin(rad)));
Bounds stampBounds = new Bounds(position, new Vector3(rotatedRange, float.MaxValue, rotatedRange));
//only look at stamps that will actually show up within the bounds of the preview
if (!stampBounds.Intersects(m_worldDesignerPreviewBounds))
{
return false;
}
else
{
return true;
}
}
///
/// Coroutine to render stamps for the world designer preview
///
///
private IEnumerator RenderStampsCoroutine()
{
m_worldDesignerRenderedStamps = 0;
for (int i = 0; i < m_worldMapStamperSettings.Count; i++)
{
//yield every 50 stamps to give unity time to catch up & free memory from render textures
if (m_worldDesignerRenderedStamps > 0 && m_worldDesignerRenderedStamps % 50 == 0)
{
ProgressBar.Show(ProgressBarPriority.TerrainLoading, "Updating Preview", "Rendering Stamps into the preview...", m_worldDesignerRenderedStamps, m_worldMapStamperSettings.Count(), false, false);
GaiaUtils.ReleaseAllTempRenderTextures();
yield return null;
}
StamperSettings stamperSettings = m_worldMapStamperSettings[i];
if (!IsStamperSettingsRelevantForPreview(stamperSettings))
{
continue;
}
StamperSettings tempCopy = PrepareWDStamperSettingsForRendering(m_worldMapStamperSettings[i]);
ApplyBrushInternal(m_worldMapPreviewOperation, tempCopy, true);
m_worldDesignerRenderedStamps++;
//tempCopy.ClearImageMaskTextures();
}
ProgressBar.Clear(ProgressBarPriority.TerrainLoading);
if (OnWorldDesignerStampRenderingFinished != null)
{
OnWorldDesignerStampRenderingFinished();
}
m_updateCoroutine = null;
GaiaUtils.ReleaseAllTempRenderTextures();
yield return null;
}
///
/// Creates a copy of world map stamper settings that are fit to be rendered on top of a base terrain stamp
///
///
///
private StamperSettings PrepareWDStamperSettingsForRendering(StamperSettings stamperSettings)
{
StamperSettings tempCopy = ScriptableObject.Instantiate(stamperSettings);
tempCopy.m_stamperInputImageMask.m_imageMaskSpace = ImageMaskSpace.World;
//transform.position = new Vector3(transform.position.x, (float)tempCopy.m_y, transform.position.z);
tempCopy.m_stamperInputImageMask.m_xOffSet = (float)tempCopy.m_x;
tempCopy.m_stamperInputImageMask.m_zOffSet = (float)tempCopy.m_z;
if (m_worldDesignerPreviewMode == WorldDesignerPreviewMode.Worldmap)
{
tempCopy.m_stamperInputImageMask.m_xScale = tempCopy.m_width / 100f;
tempCopy.m_stamperInputImageMask.m_zScale = tempCopy.m_width / 100f;
}
else
{
tempCopy.m_width *= m_worldDesignerPreviewTiles;
tempCopy.m_stamperInputImageMask.m_xScale = tempCopy.m_width / 100f;
tempCopy.m_stamperInputImageMask.m_zScale = tempCopy.m_width / 100f;
}
tempCopy.m_stamperInputImageMask.m_rotationDegrees = tempCopy.m_rotation;
tempCopy.m_rotation = 0;
tempCopy.m_width = 100;
if (tempCopy.m_imageMasks == null)
{
tempCopy.m_imageMasks = new ImageMask[0];
}
//apply the same settings for offset etc. for the following masks - we want them to appear in just the same spot as the
//main stamper input masks
foreach (ImageMask imageMask in tempCopy.m_imageMasks)
{
imageMask.m_imageMaskSpace = ImageMaskSpace.World;
if (tempCopy.m_operation == GaiaConstants.FeatureOperation.MixHeight)
{
imageMask.m_influence = ImageMaskInfluence.Global;
}
else
{
imageMask.m_influence = ImageMaskInfluence.Global;
}
imageMask.m_xOffSet = tempCopy.m_stamperInputImageMask.m_xOffSet;
imageMask.m_zOffSet = tempCopy.m_stamperInputImageMask.m_zOffSet;
imageMask.m_xScale = tempCopy.m_stamperInputImageMask.m_xScale;
imageMask.m_zScale = tempCopy.m_stamperInputImageMask.m_zScale;
imageMask.m_rotationDegrees = tempCopy.m_stamperInputImageMask.m_rotationDegrees;
}
return tempCopy;
}
///
/// Called when the stamp rendering for the world designer is complete
///
private void OnWDStampRenderFinished()
{
if (m_worldMapPreviewOperation != null)
{
m_worldMapPreviewOperation.CloseOperation();
m_worldMapPreviewOperation = null;
}
m_updateCoroutine = null;
StopEditorUpdates();
DrawStampPreview(null, true);
}
///
/// Creates a render texture from the given operation and stamper settings.
///
/// The operation to render the texture for.
/// The stamper settings to render the texture for, usually the current settings of the stamper, but other settings can be passed in as well
/// If this flag is active, the stamper will use the cached render texture of a previous run instead of the current terrain heightmap. This allows to "concatenate" multiple stamper operations together.
///
public RenderTexture ApplyBrushInternal(GaiaMultiTerrainOperation operation, StamperSettings settings, bool concatenateMode = false)
{
RenderTexture currentRT = RenderTexture.active;
if (m_stampDirty || concatenateMode || m_cachedRenderTexture == null)
{
//In concatenate mode we copy the result of a previous run on the operation heightmap render texture so that the result will be
//concatenated to what was rendered before.
if (concatenateMode)
{
Graphics.Blit(m_cachedRenderTexture, operation.RTheightmap);
}
//always populate the internal input image mask
settings.m_stamperInputImageMask.m_multiTerrainOperation = operation;
settings.m_stamperInputImageMask.m_seaLevel = m_seaLevel;
settings.m_stamperInputImageMask.m_maxWorldHeight = m_maxCurrentTerrainHeight;
settings.m_stamperInputImageMask.m_minWorldHeight = m_minCurrentTerrainHeight;
if (settings.m_imageMasks != null && settings.m_imageMasks.Length > 0)
{
//We start from a white texture, so we need the first mask action in the stack to always be "Multiply", otherwise there will be no result.
settings.m_imageMasks[0].m_blendMode = ImageMaskBlendMode.Multiply;
//Iterate through all image masks and set up the required data that masks might need to function properly
foreach (ImageMask mask in settings.m_imageMasks)
{
//mask.m_heightmapContext = heightmapContext;
//mask.m_normalmapContext = normalmapContext;
//mask.m_collisionContext = collisionContext;
mask.m_multiTerrainOperation = operation;
mask.m_seaLevel = m_seaLevel;
mask.m_maxWorldHeight = m_maxCurrentTerrainHeight;
mask.m_minWorldHeight = m_minCurrentTerrainHeight;
}
ImageMask.CheckMaskStackForInvalidTextureRules("Stamper", this.name, settings.m_imageMasks);
}
//Process the filters so that we receive our final brush texture
//Create tow separate input textures for global and local
RenderTextureDescriptor rtDescriptor = operation.RTheightmap.descriptor;
//Random write needs to be enabled for certain mask types to function!
rtDescriptor.enableRandomWrite = true;
rtDescriptor.colorFormat = RenderTextureFormat.RFloat;
RenderTexture localinputTexture = RenderTexture.GetTemporary(rtDescriptor);
RenderTexture globalInputTexture = RenderTexture.GetTemporary(rtDescriptor);
RenderTexture outputTexture = RenderTexture.GetTemporary(rtDescriptor);
if (GaiaUtils.IsStampOperation(settings.m_operation) && settings.m_stamperInputImageMask.ImageMaskTexture != null)
{
RenderTexture tempTexture = RenderTexture.GetTemporary(localinputTexture.descriptor);
RenderTexture.active = tempTexture;
GL.Clear(true, true, Color.white);
settings.m_stamperInputImageMask.Apply(tempTexture, localinputTexture);
//tempTexture.DiscardContents();
RenderTexture.ReleaseTemporary(tempTexture);
//tempTexture = null;
}
else
{
RenderTexture.active = localinputTexture;
GL.Clear(true, true, Color.white);
}
RenderTexture.active = globalInputTexture;
GL.Clear(true, true, Color.white);
RenderTexture.active = currentRT;
RenderTexture localOutputTexture = RenderTexture.GetTemporary(rtDescriptor);
RenderTexture globalOutputTexture = RenderTexture.GetTemporary(rtDescriptor);
localOutputTexture = ImageProcessing.ApplyMaskStack(localinputTexture, localOutputTexture, settings.m_imageMasks, ImageMaskInfluence.Local);
globalOutputTexture = ImageProcessing.ApplyMaskStack(globalInputTexture, globalOutputTexture, settings.m_imageMasks, ImageMaskInfluence.Global);
//Run the 2 output textures through the image mask shader for a simple multiply for the mask preview
Material multiplyMat = new Material(Shader.Find("Hidden/Gaia/FilterImageMask"));
multiplyMat.SetTexture("_InputTex", localOutputTexture);
multiplyMat.SetFloat("_Strength", 1f);
multiplyMat.SetInt("_Invert", 0);
multiplyMat.SetTexture("_ImageMaskTex", globalOutputTexture);
if (m_cachedMaskTexture != null)
{
m_cachedMaskTexture.Release();
DestroyImmediate(m_cachedMaskTexture);
}
m_cachedMaskTexture = new RenderTexture(rtDescriptor);
Graphics.Blit(globalInputTexture, m_cachedMaskTexture, multiplyMat, 0);
multiplyMat.SetTexture("_InputTex", null);
multiplyMat.SetTexture("_ImageMaskTex", null);
DestroyImmediate(multiplyMat);
switch (settings.m_operation)
{
case GaiaConstants.FeatureOperation.HydraulicErosion:
#if UNITY_EDITOR && GAIA_PRO_PRESENT
operation.RTheightmap.filterMode = FilterMode.Bilinear;
Material erosionMat = GetCurrentFXMaterial(settings);
m_Eroder.inputTextures["Height"] = operation.RTheightmap;
Vector2 texelSize = new Vector2(operation.m_originTerrain.terrainData.size.x / operation.m_originTerrain.terrainData.heightmapResolution,
operation.m_originTerrain.terrainData.size.z / operation.m_originTerrain.terrainData.heightmapResolution);
//apply Erosion settings
m_Eroder.m_ErosionSettings.m_SimScale.value = settings.m_erosionSimScale;
m_Eroder.m_ErosionSettings.m_HydroTimeDelta.value = settings.m_erosionHydroTimeDelta;
m_Eroder.m_ErosionSettings.m_HydroIterations.value = settings.m_erosionHydroIterations;
m_Eroder.m_ErosionSettings.m_ThermalTimeDelta = settings.m_erosionThermalTimeDelta;
m_Eroder.m_ErosionSettings.m_ThermalIterations = settings.m_erosionThermalIterations;
m_Eroder.m_ErosionSettings.m_ThermalReposeAngle = settings.m_erosionThermalReposeAngle;
m_Eroder.m_ErosionSettings.m_PrecipRate.value = settings.m_erosionPrecipRate;
m_Eroder.m_ErosionSettings.m_EvaporationRate.value = settings.m_erosionEvaporationRate;
m_Eroder.m_ErosionSettings.m_FlowRate.value = settings.m_erosionFlowRate;
m_Eroder.m_ErosionSettings.m_SedimentCapacity.value = settings.m_erosionSedimentCapacity;
m_Eroder.m_ErosionSettings.m_SedimentDepositRate.value = settings.m_erosionSedimentDepositRate;
m_Eroder.m_ErosionSettings.m_SedimentDissolveRate.value = settings.m_erosionSedimentDissolveRate;
m_Eroder.m_ErosionSettings.m_RiverBankDepositRate.value = settings.m_erosionRiverBankDepositRate;
m_Eroder.m_ErosionSettings.m_RiverBankDissolveRate.value = settings.m_erosionRiverBankDissolveRate;
m_Eroder.m_ErosionSettings.m_RiverBedDepositRate.value = settings.m_erosionRiverBedDepositRate;
m_Eroder.m_ErosionSettings.m_RiverBedDissolveRate.value = settings.m_erosionRiverBedDissolveRate;
//and erode
m_Eroder.ErodeHeightmap(operation.m_originTerrain.terrainData.size, operation.m_terrainDetailBrushTransform.GetBrushXYBounds(), texelSize, false);
Vector4 erosionBrushParams = new Vector4(1f, 0.0f, 0.0f, 0.0f);
erosionMat.SetTexture("_BrushTex", localOutputTexture);
erosionMat.SetTexture("_NewHeightTex", m_Eroder.outputTextures["Height"]);
erosionMat.SetVector("_BrushParams", erosionBrushParams);
operation.SetupMaterialProperties(erosionMat, MultiTerrainOperationType.Heightmap);
Graphics.Blit(operation.RTheightmap, outputTexture, erosionMat, 0);
m_Eroder.ReleaseRenderTextures();
#endif
break;
case GaiaConstants.FeatureOperation.Contrast:
operation.RTheightmap.filterMode = FilterMode.Bilinear;
Material contrastMat = GetCurrentFXMaterial(settings);
Vector4 contrastBrushParams = new Vector4(settings.m_contrastStrength, 0.0f, settings.m_contrastFeatureSize, 0);
//if (activeLocalFilters)
contrastMat.SetTexture("_BrushTex", localOutputTexture);
//else
// contrastMat.SetTexture("_BrushTex", localinputTexture);
contrastMat.SetVector("_BrushParams", contrastBrushParams);
operation.SetupMaterialProperties(contrastMat, MultiTerrainOperationType.Heightmap);
Graphics.Blit(operation.RTheightmap, outputTexture, contrastMat, 0);
break;
case GaiaConstants.FeatureOperation.Terrace:
Material terraceMat = GetCurrentFXMaterial(settings);
float delta = settings.m_terraceJitterCount * 500.0f;
//float jitteredFeatureSize = m_terraceCount + UnityEngine.Random.Range(m_terraceCount - delta, m_terraceCount + delta);
Vector4 terraceBrushParams = new Vector4(1f, settings.m_terraceCount, settings.m_terraceBevelAmountInterior, 0.0f);
//if (activeLocalFilters)
terraceMat.SetTexture("_BrushTex", localOutputTexture);
//else
// terraceMat.SetTexture("_BrushTex", localinputTexture);
terraceMat.SetVector("_BrushParams", terraceBrushParams);
operation.SetupMaterialProperties(terraceMat, MultiTerrainOperationType.Heightmap);
Graphics.Blit(operation.RTheightmap, outputTexture, terraceMat, 0);
break;
case GaiaConstants.FeatureOperation.SharpenRidges:
Material sharpenRidgesMat = GetCurrentFXMaterial(settings);
// apply brush
Vector4 sharpenRidgesBrushParams = new Vector4(
1f,
16f,
settings.m_sharpenRidgesMixStrength,
0.0f);
//if (activeLocalFilters)
sharpenRidgesMat.SetTexture("_BrushTex", localOutputTexture);
//else
// sharpenRidgesMat.SetTexture("_BrushTex", localinputTexture);
sharpenRidgesMat.SetVector("_BrushParams", sharpenRidgesBrushParams);
operation.SetupMaterialProperties(sharpenRidgesMat, MultiTerrainOperationType.Heightmap);
RenderTexture sharpenRidgesResultTex = new RenderTexture(operation.RTheightmap);
Graphics.Blit(operation.RTheightmap, sharpenRidgesResultTex, sharpenRidgesMat, 0);
//Perform Iterations
RenderTexture sharpenRidgesIterationTex = new RenderTexture(sharpenRidgesResultTex);
for (int i = 1; i <= settings.m_sharpenRidgesIterations; i++)
{
Graphics.Blit(sharpenRidgesResultTex, sharpenRidgesIterationTex, sharpenRidgesMat, 0);
Graphics.Blit(sharpenRidgesIterationTex, sharpenRidgesResultTex);
}
Graphics.Blit(sharpenRidgesResultTex, outputTexture, sharpenRidgesMat, 0);
sharpenRidgesIterationTex.Release();
sharpenRidgesResultTex.Release();
#if UNITY_EDITOR
DestroyImmediate(sharpenRidgesIterationTex);
DestroyImmediate(sharpenRidgesResultTex);
#endif
break;
case GaiaConstants.FeatureOperation.HeightTransform:
Material heightTransformMat = GetCurrentFXMaterial(settings);
heightTransformMat.SetTexture("_InputTex", operation.RTheightmap);
heightTransformMat.SetTexture("_BrushTex", localOutputTexture);
Terrain currentTerrain = GetCurrentTerrain();
float scalarMaxHeight = Mathf.InverseLerp(0, currentTerrain.terrainData.size.y, m_maxCurrentTerrainHeight);
float scalarMinHeight = Mathf.InverseLerp(0, currentTerrain.terrainData.size.y, m_minCurrentTerrainHeight);
//transfer the scalar 0..1 value to -0.5..0.5 as this is how it is used in the shader
scalarMaxHeight = Mathf.Lerp(0, 0.5f, scalarMaxHeight);
scalarMinHeight = Mathf.Lerp(0, 0.5f, scalarMinHeight);
heightTransformMat.SetFloat("_MaxWorldHeight", scalarMaxHeight);
heightTransformMat.SetFloat("_MinWorldHeight", scalarMinHeight);
operation.SetupMaterialProperties(heightTransformMat, MultiTerrainOperationType.Heightmap);
ImageProcessing.BakeCurveTexture(settings.m_heightTransformCurve, heightTransformCurveTexture);
heightTransformMat.SetTexture("_HeightTransformTex", heightTransformCurveTexture);
Graphics.Blit(operation.RTheightmap, outputTexture, heightTransformMat, 0);
break;
case GaiaConstants.FeatureOperation.PowerOf:
Material powerOfMat = GetCurrentFXMaterial(settings);
powerOfMat.SetTexture("_InputTex", operation.RTheightmap);
powerOfMat.SetTexture("_BrushTex", localOutputTexture);
powerOfMat.SetFloat("_Power", settings.m_powerOf);
operation.SetupMaterialProperties(powerOfMat, MultiTerrainOperationType.Heightmap);
Graphics.Blit(operation.RTheightmap, outputTexture, powerOfMat, 0);
break;
case GaiaConstants.FeatureOperation.Smooth:
Vector4 smoothBrushParams = new Vector4(1f, 0.0f, 0.0f, 0.0f);
Material smoothMat = GetCurrentFXMaterial(settings);
m_heightModifier = ImageMask.NewAnimCurveStraightUpwards();
ImageProcessing.BakeCurveTexture(m_heightModifier, transformHeightCurveTexture);
smoothMat.SetTexture("_MainTex", operation.RTheightmap);
smoothMat.SetTexture("_BrushTex", localinputTexture);
smoothMat.SetTexture("_HeightTransformTex", transformHeightCurveTexture);
smoothMat.SetVector("_BrushParams", smoothBrushParams);
Vector4 smoothWeights = new Vector4(
Mathf.Clamp01(1.0f - Mathf.Abs(settings.m_smoothVerticality)), // centered
Mathf.Clamp01(-settings.m_smoothVerticality), // min
Mathf.Clamp01(settings.m_smoothVerticality), // max
settings.m_smoothBlurRadius); // kernel size
smoothMat.SetVector("_SmoothWeights", smoothWeights);
operation.SetupMaterialProperties(smoothMat, MultiTerrainOperationType.Heightmap);
// Two pass blur (first horizontal, then vertical)
RenderTexture workaround1 = RenderTexture.GetTemporary(operation.RTheightmap.descriptor);
RenderTexture tmpsmoothRT = RenderTexture.GetTemporary(operation.RTheightmap.descriptor);
Graphics.Blit(operation.RTheightmap, tmpsmoothRT, smoothMat, 0);
Graphics.Blit(tmpsmoothRT, outputTexture, smoothMat, 1);
RenderTexture.ReleaseTemporary(tmpsmoothRT);
RenderTexture.ReleaseTemporary(workaround1);
break;
case GaiaConstants.FeatureOperation.MixHeight:
Material mixHeightMat = GetCurrentFXMaterial(settings);
mixHeightMat.SetTexture("_InputTex", operation.RTheightmap);
mixHeightMat.SetTexture("_BrushTex", localOutputTexture);
mixHeightMat.SetTexture("_GlobalBrushTex", globalOutputTexture);
mixHeightMat.SetFloat("_MixMidPoint", settings.m_mixMidPoint);
mixHeightMat.SetFloat("_Strength", settings.m_mixHeightStrength);
float size = settings.m_stamperInputImageMask.m_multiTerrainOperation.m_originTerrain != null ? settings.m_stamperInputImageMask.m_multiTerrainOperation.m_originTerrain.terrainData.size.x : settings.m_stamperInputImageMask.m_multiTerrainOperation.m_range * 2;
if (settings.m_stamperInputImageMask.m_imageMaskSpace == ImageMaskSpace.World)
{
settings.m_stamperInputImageMask.m_xOffSetScalar = (settings.m_stamperInputImageMask.m_xOffSet + settings.m_stamperInputImageMask.m_multiTerrainOperation.m_originTransform.position.x) / size;
settings.m_stamperInputImageMask.m_zOffSetScalar = (settings.m_stamperInputImageMask.m_zOffSet + settings.m_stamperInputImageMask.m_multiTerrainOperation.m_originTransform.position.z) / size;
}
else
{
settings.m_stamperInputImageMask.m_xOffSetScalar = settings.m_stamperInputImageMask.m_xOffSet / size;
settings.m_stamperInputImageMask.m_zOffSetScalar = settings.m_stamperInputImageMask.m_zOffSet / size;
}
mixHeightMat.SetFloat("_XOffset", settings.m_stamperInputImageMask.m_xOffSetScalar);
mixHeightMat.SetFloat("_ZOffset", settings.m_stamperInputImageMask.m_zOffSetScalar);
mixHeightMat.SetFloat("_XScale", settings.m_stamperInputImageMask.m_xScale);
mixHeightMat.SetFloat("_ZScale", settings.m_stamperInputImageMask.m_zScale);
mixHeightMat.SetFloat("_Rotation", -1 * (float)((Math.PI / 180) * settings.m_stamperInputImageMask.m_rotationDegrees)); //multiply by -1 to bring it into alignment with the slider on the UI, right on the slider => rotate to the right
mixHeightMat.SetInt("_Tiling", settings.m_stamperInputImageMask.m_tiling ? 1 : 0);
if (operation.m_originTerrain != null)
{
mixHeightMat.SetFloat("_WorldHeightMin", Mathf.InverseLerp(0, operation.m_originTerrain.terrainData.size.y, m_minCurrentTerrainHeight));
mixHeightMat.SetFloat("_WorldHeightMax", Mathf.InverseLerp(0, operation.m_originTerrain.terrainData.size.y, m_maxCurrentTerrainHeight));
}
else
{
mixHeightMat.SetFloat("_WorldHeightMin", Mathf.InverseLerp(0, TerrainLoaderManager.Instance.TerrainSceneStorage.m_worldMapPreviewTerrainHeight, m_minCurrentTerrainHeight));
mixHeightMat.SetFloat("_WorldHeightMax", Mathf.InverseLerp(0, TerrainLoaderManager.Instance.TerrainSceneStorage.m_worldMapPreviewTerrainHeight, m_maxCurrentTerrainHeight));
}
operation.SetupMaterialProperties(mixHeightMat, MultiTerrainOperationType.Heightmap);
Graphics.Blit(operation.RTheightmap, outputTexture, mixHeightMat, 0);
break;
default:
Material mat = new Material(Shader.Find("Hidden/Gaia/BaseOperation"));
Vector4 brushParams = new Vector4(0.01f * settings.m_height, 0.0f, 0.0f, 0.0f);
if (settings.m_operation == GaiaConstants.FeatureOperation.AddHeight || settings.m_operation == GaiaConstants.FeatureOperation.SubtractHeight)
{
mat.SetTexture("_BrushTex", globalOutputTexture);
mat.SetTexture("_GlobalMaskTex", localOutputTexture);
}
else
{
mat.SetTexture("_BrushTex", localOutputTexture);
mat.SetTexture("_GlobalMaskTex", globalOutputTexture);
}
mat.SetFloat("_BaseLevel", Mathf.Lerp(0f, 0.5f, settings.m_baseLevel));
mat.SetFloat("_BlendStrength", settings.m_blendStrength);
//Get relative y position according to terrain height where
//-1 = terrain y-position - max terrain height
//+1 = terrain y-position + max terrain height
//this covers the complete y range where a stamp could potentially still influence the terrain.
//For the world map preview we assume or non-existing terrain sits at y=0 and the max min height is the height set for the preview render in the Gaia constants
float potentialYMin = operation.m_originTerrain != null ? operation.m_originTerrain.transform.position.y - operation.m_originTerrain.terrainData.size.y : -TerrainLoaderManager.Instance.TerrainSceneStorage.m_worldMapPreviewTerrainHeight;
float potentialYMax = operation.m_originTerrain != null ? operation.m_originTerrain.transform.position.y + operation.m_originTerrain.terrainData.size.y : +TerrainLoaderManager.Instance.TerrainSceneStorage.m_worldMapPreviewTerrainHeight;
float relativeYPos = Mathf.Lerp(-0.5f, 0.5f, Mathf.InverseLerp(potentialYMin, potentialYMax, (float)settings.m_y));
mat.SetFloat("_yPos", relativeYPos);
if (settings.m_drawStampBase)
mat.SetFloat("_StampBase", 1f);
else
mat.SetFloat("_StampBase", 0f);
if (settings.m_adaptiveBase)
mat.SetFloat("_AdaptiveBase", 1f);
else
mat.SetFloat("_AdaptiveBase", 0f);
mat.SetVector("_BrushParams", brushParams);
operation.SetupMaterialProperties(mat, MultiTerrainOperationType.Heightmap);
Graphics.Blit(operation.RTheightmap, outputTexture, mat, (int)settings.m_operation);
mat.SetTexture("_BrushTex", null);
mat.SetTexture("_GlobalMaskTex", null);
DestroyImmediate(mat);
break;
}
if (m_cachedRenderTexture != null)
{
m_cachedRenderTexture.Release();
DestroyImmediate(m_cachedRenderTexture);
}
//save result in cache
m_cachedRenderTexture = new RenderTexture(rtDescriptor);
m_cachedRenderTexture.name = "Stamper Cached Render Texture";
Graphics.Blit(outputTexture, m_cachedRenderTexture);
RenderTexture.active = currentRT;
//Clean up render textures
if (localinputTexture != null)
{
//localinputTexture.DiscardContents();
RenderTexture.ReleaseTemporary(localinputTexture);
//localinputTexture = null;
}
if (globalInputTexture != null)
{
//globalInputTexture.DiscardContents();
RenderTexture.ReleaseTemporary(globalInputTexture);
//globalInputTexture = null;
}
if (localOutputTexture != null)
{
//localOutputTexture.DiscardContents();
RenderTexture.ReleaseTemporary(localOutputTexture);
//localOutputTexture = null;
}
if (globalOutputTexture != null)
{
//globalOutputTexture.DiscardContents();
RenderTexture.ReleaseTemporary(globalOutputTexture);
//globalOutputTexture = null;
}
if (outputTexture != null)
{
//outputTexture.DiscardContents();
RenderTexture.ReleaseTemporary(outputTexture);
//outputTexture = null;
}
m_stampDirty = false;
}
return m_cachedRenderTexture;
//else
//{
// //no re-calculation necessary, just return last result from cache
// return m_cachedRenderTexture;
//}
}
private Material GetCurrentFXMaterial(StamperSettings settings)
{
string shaderName = "";
switch (settings.m_operation)
{
case GaiaConstants.FeatureOperation.Contrast:
shaderName = "Hidden/GaiaPro/Contrast";
break;
case GaiaConstants.FeatureOperation.Terrace:
shaderName = "Hidden/GaiaPro/Terrace";
break;
case GaiaConstants.FeatureOperation.SharpenRidges:
shaderName = "Hidden/GaiaPro/SharpenRidges";
break;
case GaiaConstants.FeatureOperation.HydraulicErosion:
shaderName = "Hidden/GaiaPro/SimpleHeightBlend";
break;
case GaiaConstants.FeatureOperation.HeightTransform:
shaderName = "Hidden/GaiaPro/HeightTransform";
break;
case GaiaConstants.FeatureOperation.PowerOf:
shaderName = "Hidden/GaiaPro/PowerOf";
break;
case GaiaConstants.FeatureOperation.Smooth:
shaderName = "Hidden/Gaia/SmoothHeight";
break;
case GaiaConstants.FeatureOperation.MixHeight:
shaderName = "Hidden/Gaia/MixHeight";
break;
default:
break;
}
if (shaderName == "")
{
return null;
}
if (m_currentFXMaterial == null || m_currentFXMaterial.shader.name != shaderName)
m_currentFXMaterial = new Material(Shader.Find(shaderName));
return m_currentFXMaterial;
}
private void BakeCurveTextures()
{
if (m_MaskTexturesDirty)
{
float range = 1f;
if (m_distanceMask != null && m_distanceMask.length > 0)
{
range = m_distanceMask[m_distanceMask.length - 1].time;
for (float i = 0f; i <= 1f; i += 1f / 255f)
{
float c = m_distanceMask.Evaluate(i * range);
distanceMaskCurveTexture.SetPixel(Mathf.FloorToInt(i * 255f), 0, new Color(c, c, c));
}
distanceMaskCurveTexture.Apply();
}
range = 1f;
if (m_heightModifier != null && m_heightModifier.length > 0)
{
range = m_heightModifier[m_heightModifier.length - 1].time;
for (float i = 0f; i <= 1f; i += 1f / 255f)
{
float c = m_heightModifier.Evaluate(i * range);
transformHeightCurveTexture.SetPixel(Mathf.FloorToInt(i * 255f), 0, new Color(c, c, c));
}
transformHeightCurveTexture.Apply();
}
m_MaskTexturesDirty = false;
}
}
///
/// Generate a small white texture for the image mask when no image mask is selected to always have full mask influence in the shader
///
public void EmptyImageMask()
{
m_imageMask = Texture2D.whiteTexture;
m_imageMaskGUID = "";
m_imageMaskInvert = false;
m_imageMaskNormalise = false;
}
///
/// Generate a small white texture for the stamp image when no stamp image is selected to always have full stamp influence in the shader
///
public void EmptyStampImage()
{
m_stampImage = Texture2D.whiteTexture;
m_stampImageGUID = "";
m_invertStamp = false;
m_normaliseStamp = false;
}
///
/// Calculate the height to apply to the location supplied
///
/// The terrain height at this location
/// The raw unadsjusted source map height at this location
/// The adjusted source map height at this location
/// The stencil height in normal units
/// The strength of the effect 0 - no effect - 1 - full effect
/// New height
private float CalculateHeight(float terrainHeight, float smHeightRaw, float smHeightAdj, float stencilHeightNU, float strength)
{
float tmpHeight = 0f;
float heightDiff = 0f;
//Check for the base
if (m_settings.m_drawStampBase != true)
{
if (smHeightRaw < m_settings.m_baseLevel)
{
return terrainHeight;
}
}
switch (m_settings.m_operation)
{
case GaiaConstants.FeatureOperation.RaiseHeight:
{
if (smHeightAdj > terrainHeight)
{
heightDiff = (smHeightAdj - terrainHeight) * strength;
terrainHeight += heightDiff;
}
}
break;
case GaiaConstants.FeatureOperation.BlendHeight:
{
tmpHeight = (m_settings.m_blendStrength * smHeightAdj) + ((1f - m_settings.m_blendStrength) * terrainHeight);
heightDiff = (tmpHeight - terrainHeight) * strength;
terrainHeight += heightDiff;
}
break;
case GaiaConstants.FeatureOperation.SetHeight:
{
tmpHeight = terrainHeight + (smHeightAdj * stencilHeightNU);
heightDiff = (tmpHeight - terrainHeight) * strength;
terrainHeight += heightDiff;
}
break;
case GaiaConstants.FeatureOperation.LowerHeight:
{
if (smHeightAdj < terrainHeight)
{
heightDiff = (terrainHeight - smHeightAdj) * strength;
terrainHeight -= heightDiff;
}
}
break;
}
return terrainHeight;
}
///
/// Rotate the point around the pivot - used to handle rotation
///
/// Point to move
/// Pivot
/// Angle to pivot
///
private Vector3 RotatePointAroundPivot(Vector3 point, Vector3 pivot, Vector3 angle)
{
Vector3 dir = point - pivot;
dir = Quaternion.Euler(angle) * dir;
point = dir + pivot;
return point;
}
#endregion
}
class RotationProducts
{
public double sinTheta = 0;
public double cosTheta = 0;
}
}