using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; #if UNITY_EDITOR using UnityEditorInternal; using UnityEditor; #endif using UnityEngine; using System.Linq; namespace Gaia { public enum ImageMaskOperation { CollisionMask = 5, DistanceMask = 1, HeightMask = 2, ImageMask = 0, NoiseMask = 4, SlopeMask = 3, Smooth = 8, StrengthTransform = 6, TerrainTexture = 9, WorldBiomeMask = 11, PROConcaveConvex = 10, PROHydraulicErosion = 7 } public enum ImageMaskBlendMode { Multiply, GreaterThan, SmallerThan, Add, Subtract } public enum ImageMaskDistanceMaskAxes {[Description("XZ Circular")] XZ, [Description("X only")] X, [Description("Z Only")] Z } public enum ImageMaskInfluence { Local, Global } /// /// Toggle between two different ways of handling height masks /// Absolute will store the minimum and maximum value for the mask as absolute values relative to the sea levele /// Relative will store the minimum and maximum value for the mask as absolute values relative to the sea levele /// public enum HeightMaskType { WorldSpace, RelativeToSeaLevel } public enum HeightMaskUnit { Meter, Percent} [System.Serializable] public class ImageMask { public bool m_active = true; public bool m_invert = false; public bool m_hasErrors = false; public ImageMaskInfluence m_influence = ImageMaskInfluence.Local; public ImageMaskOperation m_operation; public ImageMaskBlendMode m_blendMode; public float m_strength = 1f; public float m_seaLevel = 0f; //The maximum terrain height, NOT the theoretical maximum height, but the highest measured physical point on the terrain public float m_maxWorldHeight = 0f; //The minimum terrain height, NOT the theoretical minimum height, but the lowest measured physical point on the terrain public float m_minWorldHeight = 0f; public float m_xOffSet = 0f; public float m_zOffSet = 0f; public float m_xOffSetScalar = 0f; public float m_zOffSetScalar = 0f; //The current multi-terrain op we are working on - used to get heightmap, normalmap etc. for the affected area [NonSerialized] public GaiaMultiTerrainOperation m_multiTerrainOperation; //Image Mask specific public Texture2D ImageMaskTexture { get{ if (m_imageMaskTexture == null) { if (!string.IsNullOrEmpty(m_imageMaskTextureGUID)) { #if UNITY_EDITOR m_imageMaskTexture = (Texture2D)AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(m_imageMaskTextureGUID), typeof(Texture2D)); #endif } } return m_imageMaskTexture; } set{ if (value != m_imageMaskTexture) { m_imageMaskTexture = value; if (value != null) { #if UNITY_EDITOR m_imageMaskTextureGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(value)); #endif } else { m_imageMaskTextureGUID = ""; } } } } private Texture2D m_imageMaskTexture; [SerializeField] private string m_imageMaskTextureGUID; public GaiaConstants.ImageMaskFilterMode m_imageMaskFilterMode; public Color m_imageMaskColorSelectionColor = Color.white; public float m_imageMaskColorSelectionAccuracy = 0.5f; //distance Mask specific public AnimationCurve m_distanceMaskCurve = new AnimationCurve(new Keyframe[2] { new Keyframe() { time = 0, value = 1, weightedMode = WeightedMode.None }, new Keyframe() { time = 1, value = 0, weightedMode = WeightedMode.None } }); //height Mask specific public AnimationCurve m_heightMaskCurve = new AnimationCurve(new Keyframe[2] { new Keyframe() { time = 0, value = 0, weightedMode = WeightedMode.None }, new Keyframe() { time = 1, value = 1, weightedMode = WeightedMode.None } }); //strength Transform specific public AnimationCurve m_strengthTransformCurve = NewAnimCurveStraightUpwards(); public HeightMaskType m_heightMaskType = HeightMaskType.RelativeToSeaLevel; [SerializeField] private HeightMaskUnit m_heightMaskUnit = HeightMaskUnit.Percent; public float m_relativeHeightMin = 25f; public float m_relativeHeightMax = 75f; public HeightMaskUnit HeightMaskUnit { get { return m_heightMaskUnit; } set { if (value != m_heightMaskUnit) { //Get the meter value for 1% of the terrain height - makes for an easy conversion. float worldHeightDifference = m_maxWorldHeight - m_minWorldHeight; float worldHeightPercentInMeter = worldHeightDifference / 100f; if (value == HeightMaskUnit.Meter) { //Value was in % before - convert all values to meter accordingly m_absoluteHeightMin *= worldHeightPercentInMeter; m_absoluteHeightMax *= worldHeightPercentInMeter; m_relativeHeightMin *= worldHeightPercentInMeter; m_relativeHeightMax *= worldHeightPercentInMeter; } else { //Value was in Meter before - convert all values to % accordingly m_absoluteHeightMin /= worldHeightPercentInMeter; m_absoluteHeightMax /= worldHeightPercentInMeter; m_relativeHeightMin /= worldHeightPercentInMeter; m_relativeHeightMax /= worldHeightPercentInMeter; } } m_heightMaskUnit = value; } } //The absolute minimum height for the heightmask selection, e.g. "the selection starts at 50 meters" public float m_absoluteHeightMin = 50; //The absolute maximum height for the heightmask selection, e.g. "the selection ends at 150 meters" public float m_absoluteHeightMax = 150; //This is a legacy field that is not really used in the height mask anymore, but serves as a flag to detect whether //a height mask has been created under an older data structure - it will then automatically be migrated when the mask is used. public float m_seaLevelRelativeHeightMin = -109876.54321f; //This is a legacy field that is not really used in the height mask anymore, but serves as a flag to detect whether //a height mask has been created under an older data structure - it will then automatically be migrated when the mask is used. public float m_seaLevelRelativeHeightMax = 109876.54321f; public bool tree1active = false; public bool tree2active = false; public AnimationCurve m_slopeMaskCurve = NewAnimCurveStraightUpwards(); public float m_slopeMin = 0.0f; public float m_slopeMax = 0.1f; public ImageMaskDistanceMaskAxes m_distanceMaskAxes; public GaiaNoiseSettings m_gaiaNoiseSettings = new GaiaNoiseSettings(); #if UNITY_EDITOR public NoiseSettings m_noiseSettings; public NoiseToolSettings m_noiseToolSettings = new NoiseToolSettings(); public NoiseSettingsGUI noiseSettingsGUI; #endif public bool m_ShowNoiseTransformSettings = false; public bool m_ShowNoisePreviewTexture = true; public bool m_noisePreviewTextureLocked = false; public bool m_ShowNoiseTypeSettings = false; public bool m_scaleNoiseToTerrainSize = false; private Texture2D m_distanceMaskCurveTexture; private Texture2D distanceMaskCurveTexture { get { return ImageProcessing.CreateMaskCurveTexture(ref m_distanceMaskCurveTexture); } } private Texture2D m_heightMaskCurveTexture; private Texture2D heightMaskCurveTexture { get { return ImageProcessing.CreateMaskCurveTexture(ref m_heightMaskCurveTexture); } } private Texture2D m_slopeMaskCurveTexture; private Texture2D slopeMaskCurveTexture { get { return ImageProcessing.CreateMaskCurveTexture(ref m_slopeMaskCurveTexture); } } private Texture2D m_strengthTransformCurveTexture; private Texture2D strengthTransformCurveTexture { get { return ImageProcessing.CreateMaskCurveTexture(ref m_strengthTransformCurveTexture); } } //collision mask specific public bool m_collisionMaskExpanded = true; public CollisionMask[] m_collisionMasks = new CollisionMask[0]; #if UNITY_EDITOR public ReorderableList m_reorderableCollisionMaskList; #endif #region Erosion Settings //Eroder class reference for the erosion feature #if UNITY_EDITOR && GAIA_PRO_PRESENT private HydraulicEroder m_Eroder = null; #endif public GaiaConstants.ErosionMaskOutput m_erosionMaskOutput = GaiaConstants.ErosionMaskOutput.Sediment; public float m_erosionSimScale = 9f; public float m_erosionHydroTimeDelta = 0.05f; public int m_erosionHydroIterations = 15; public float m_erosionThermalTimeDelta = 0.01f; public int m_erosionThermalIterations = 80; public int m_erosionThermalReposeAngle = 80; public float m_erosionPrecipRate = 0.5f; public float m_erosionEvaporationRate = 0.5f; public float m_erosionFlowRate = 0.5f; public float m_erosionSedimentCapacity = 0.5f; public float m_erosionSedimentDepositRate = 0.8f; public float m_erosionSedimentDissolveRate = 0.5f; public float m_erosionRiverBankDepositRate = 7.0f; public float m_erosionRiverBankDissolveRate = 5.0f; public float m_erosionRiverBedDepositRate = 5.0f; public float m_erosionRiverBedDissolveRate = 5.0f; public bool m_erosionShowAdvancedUI; public bool m_erosionShowThermalUI; public bool m_erosionShowWaterUI; public bool m_erosionShowSedimentUI; public bool m_erosionShowRiverBankUI; #endregion //smooth settings public float m_smoothVerticality = 0f; public float m_smoothBlurRadius = 1f; //Texture mask settings //public int m_textureLayerId = 0; public string m_textureMaskSpawnRuleGUID = ""; public static SpawnRule[] m_allTextureSpawnRules; public static Spawner[] m_allTextureSpawners; public static string[] m_allTextureSpawnRuleNames; public static int[] m_allTextureSpawnRuleIndices; //Convex Concave settings public float m_concavity = 1f; public float m_concavityFeatureSize = 10f; private ComputeShader m_concavityShader; public bool m_foldedOut = true; public string m_selectedWorldBiomeMaskGUID; public void FreeTextureReferences() { m_imageMaskTexture = null; } public void CheckHeightMaskMigration() { if (m_seaLevelRelativeHeightMax != 109876.54321f || m_seaLevelRelativeHeightMin != -109876.54321f) { //This is a height mask created under the old height mask model, needs to be migrated switch (m_heightMaskType) { case HeightMaskType.WorldSpace: //This height mask was using the old "Absolute" setting before //This equals "Relative to Sea Level in Meters" in the new logic m_heightMaskType = HeightMaskType.RelativeToSeaLevel; m_heightMaskUnit = HeightMaskUnit.Meter; m_relativeHeightMin = m_seaLevelRelativeHeightMin; m_relativeHeightMax = m_seaLevelRelativeHeightMax; m_absoluteHeightMin = m_relativeHeightMin + m_seaLevel; m_absoluteHeightMax = m_relativeHeightMax + m_seaLevel; break; case HeightMaskType.RelativeToSeaLevel: //This height mask was using the old "Relative" setting before //This equals "World Space in Percent" in the new logic m_heightMaskType = HeightMaskType.WorldSpace; m_heightMaskUnit = HeightMaskUnit.Percent; m_absoluteHeightMin = m_relativeHeightMin; m_absoluteHeightMax = m_relativeHeightMax; float worldHeightDifference = Mathf.Max(1f,m_maxWorldHeight - m_minWorldHeight); float worldHeightPercentInMeter = worldHeightDifference / 100f; float seaLevelInPercent = m_seaLevel / worldHeightPercentInMeter; m_relativeHeightMin = m_absoluteHeightMin - seaLevelInPercent; m_relativeHeightMax = m_absoluteHeightMax - seaLevelInPercent; break; } //fill in the magic numbers to mark the migration as complete m_seaLevelRelativeHeightMin = -109876.54321f; m_seaLevelRelativeHeightMax = 109876.54321f; } } /// /// Applies the mask to an input render texture and returns the result as render texture. /// /// The input texture /// The processed output in a render texture. public RenderTexture Apply(RenderTexture inputTexture, RenderTexture outputTexture) { RenderTexture currentRT = RenderTexture.active; #if UNITY_EDITOR #if GAIA_PRO_PRESENT //clean up eroder if not in use anymore if (m_Eroder != null && m_operation != ImageMaskOperation.PROHydraulicErosion) { ClearEroder(); } #endif Material filterMat = GetCurrentFXFilterMaterial(); if (filterMat == null) { Debug.LogWarning("Could not find a filter material for operation " + m_operation.ToString()); return inputTexture; } filterMat.SetTexture("_InputTex", inputTexture); filterMat.SetFloat("_Strength", m_strength); if (m_operation != ImageMaskOperation.PROHydraulicErosion) { filterMat.SetInt("_Invert", m_invert ? 1 : 0); } else { //Special treatement for the hydraulic erosion mask: Flip the invert flag because the erosion map data seems to be inverted already filterMat.SetInt("_Invert", m_invert ? 0 : 1); } if (m_operation == ImageMaskOperation.NoiseMask && IsDefaultStrenghtCurve()) { m_strengthTransformCurve = NewAnimCurveDefaultNoise(); } ImageProcessing.BakeCurveTexture(m_strengthTransformCurve, strengthTransformCurveTexture); filterMat.SetTexture("_HeightTransformTex", strengthTransformCurveTexture); switch (m_operation) { case ImageMaskOperation.ImageMask: #if !GAIA_PRO_PRESENT if (m_imageMaskFilterMode != GaiaConstants.ImageMaskFilterMode.PROColorSelection) { #endif filterMat.SetTexture("_ImageMaskTex", ImageMaskTexture); filterMat.SetInt("_FilterMode", (int)m_imageMaskFilterMode); filterMat.SetColor("_Color", m_imageMaskColorSelectionColor); filterMat.SetFloat("_ColorAccuracy", m_imageMaskColorSelectionAccuracy); filterMat.SetFloat("_XOffset", m_xOffSetScalar); filterMat.SetFloat("_ZOffset", m_zOffSetScalar); Graphics.Blit(inputTexture, outputTexture, filterMat, (int)m_blendMode); filterMat.SetTexture("_ImageMaskTex", null); #if !GAIA_PRO_PRESENT } else { Graphics.Blit(inputTexture, outputTexture); } #endif break; case ImageMaskOperation.DistanceMask: ImageProcessing.BakeCurveTexture(m_distanceMaskCurve, distanceMaskCurveTexture); filterMat.SetTexture("_DistanceMaskTex", distanceMaskCurveTexture); filterMat.SetFloat("_XOffset", m_xOffSetScalar); filterMat.SetFloat("_ZOffset", m_zOffSetScalar); filterMat.SetFloat("_AxisMode", (int)m_distanceMaskAxes); Graphics.Blit(inputTexture, outputTexture, filterMat, (int)m_blendMode); filterMat.SetTexture("_DistanceMaskTex", null); break; case ImageMaskOperation.HeightMask: ImageProcessing.BakeCurveTexture(m_heightMaskCurve, heightMaskCurveTexture); filterMat.SetTexture("_HeightMapTex", m_multiTerrainOperation.RTheightmap); filterMat.SetTexture("_HeightMaskTex", heightMaskCurveTexture); //calculate the correct scalar min max height values according to the current terrain and the sea level Terrain currentTerrain = m_multiTerrainOperation.m_originTerrain; float scalarSeaLevel = Mathf.InverseLerp(0, currentTerrain.terrainData.size.y, m_seaLevel); float m_scalarMaxHeight = 0.5f; float m_scalarMinHeight = 0f; float minHeightInMeters = m_absoluteHeightMin; float maxHeightInMeters = m_absoluteHeightMax; float worldHeightDifference = m_maxWorldHeight - m_minWorldHeight; float worldHeightPercentInMeter = worldHeightDifference / 100f; if (m_heightMaskType == HeightMaskType.WorldSpace) { if (m_heightMaskUnit == HeightMaskUnit.Percent) { minHeightInMeters *= worldHeightPercentInMeter; maxHeightInMeters *= worldHeightPercentInMeter; } } else { if (m_heightMaskUnit == HeightMaskUnit.Percent) { minHeightInMeters = (m_relativeHeightMin * worldHeightPercentInMeter) + m_seaLevel; maxHeightInMeters = (m_relativeHeightMax * worldHeightPercentInMeter) + m_seaLevel; } else { minHeightInMeters = m_relativeHeightMin + m_seaLevel; maxHeightInMeters = m_relativeHeightMax + m_seaLevel; } } m_scalarMaxHeight = Mathf.InverseLerp(0, currentTerrain.terrainData.size.y, maxHeightInMeters); m_scalarMinHeight = Mathf.InverseLerp(0, currentTerrain.terrainData.size.y, minHeightInMeters); //transfer the scalar 0..1 value to -0.5..0.5 as this is how it is used in the shader m_scalarMaxHeight = Mathf.Lerp(0, 0.5f, m_scalarMaxHeight); m_scalarMinHeight = Mathf.Lerp(0, 0.5f, m_scalarMinHeight); filterMat.SetFloat("_MinHeight", m_scalarMinHeight); filterMat.SetFloat("_MaxHeight", m_scalarMaxHeight); Graphics.Blit(inputTexture, outputTexture, filterMat, (int)m_blendMode); filterMat.SetTexture("_HeightMapTex", null); filterMat.SetTexture("_HeightMaskTex", null); break; case ImageMaskOperation.SlopeMask: ImageProcessing.BakeCurveTexture(m_slopeMaskCurve, slopeMaskCurveTexture); filterMat.SetTexture("_NormalMapTex", m_multiTerrainOperation.RTnormalmap); filterMat.SetTexture("_SlopeMaskTex", slopeMaskCurveTexture); filterMat.SetFloat("_MinSlope", m_slopeMin); filterMat.SetFloat("_MaxSlope", m_slopeMax); Graphics.Blit(inputTexture, outputTexture, filterMat, (int)m_blendMode); filterMat.SetTexture("_NormalMapTex", null); filterMat.SetTexture("_SlopeMaskTex", null); break; case ImageMaskOperation.NoiseMask: //noise settings can be null when the mask was never viewed in the inspector, e.g. from autospawning if (m_noiseSettings == null) { m_noiseSettings = (NoiseSettings)ScriptableObject.CreateInstance(typeof(NoiseSettings)); //Try to initialize from our own Gaia Noise Settings m_noiseSettings.transformSettings.translation = m_gaiaNoiseSettings.m_translation; m_noiseSettings.transformSettings.rotation = m_gaiaNoiseSettings.m_rotation; m_noiseSettings.transformSettings.scale = m_gaiaNoiseSettings.m_scale; m_noiseSettings.domainSettings.noiseTypeName = m_gaiaNoiseSettings.m_noiseTypeName; m_noiseSettings.domainSettings.noiseTypeParams = m_gaiaNoiseSettings.m_noiseTypeParams; m_noiseSettings.domainSettings.fractalTypeName = m_gaiaNoiseSettings.m_fractalTypeName; m_noiseSettings.domainSettings.fractalTypeParams = m_gaiaNoiseSettings.m_fractalTypeParams; } float previewSize = 1 / m_multiTerrainOperation.m_originTerrain.terrainData.size.x; // get proper noise material from current noise settings NoiseSettings noiseSettings = m_noiseSettings; Material matNoise = NoiseUtils.GetDefaultBlitMaterial(m_noiseSettings); // setup the noise material with values in noise settings m_noiseSettings.SetupMaterial(matNoise); // convert brushRotation to radians float brushRotation = 0; //brushRotation *= Mathf.PI / 180; Vector3 brushPosWS = m_multiTerrainOperation.m_originTransform.position + (Vector3)TerrainLoaderManager.Instance.GetOrigin(); float brushSize = m_multiTerrainOperation.m_range; //Adjust scaling depending on whether we want to scale up with terrain size if (m_scaleNoiseToTerrainSize) { bool isWorldSpace = (m_noiseToolSettings.coordSpace == CoordinateSpace.World); brushSize = isWorldSpace ? brushSize * previewSize : 1; brushPosWS = isWorldSpace ? brushPosWS * previewSize : Vector3.zero; } else { brushSize = m_multiTerrainOperation.m_range / 512; brushPosWS = (brushPosWS / m_multiTerrainOperation.m_originTerrain.terrainData.size.x) * ( m_multiTerrainOperation.m_originTerrain.terrainData.size.x / m_multiTerrainOperation.m_range) * brushSize ; } // // override noise transform Quaternion rotQ = Quaternion.AngleAxis(-brushRotation, Vector3.up); Matrix4x4 translation = Matrix4x4.Translate(brushPosWS); Matrix4x4 rotation = Matrix4x4.Rotate(rotQ); Matrix4x4 scale = Matrix4x4.Scale(Vector3.one * brushSize); Matrix4x4 noiseToWorld = translation * scale; matNoise.SetMatrix(NoiseSettings.ShaderStrings.transform, m_noiseSettings.trs * noiseToWorld); RenderTexture noiseRT = RenderTexture.GetTemporary(m_multiTerrainOperation.RTheightmap.descriptor); int noisePass = NoiseUtils.kNumBlitPasses * NoiseLib.GetNoiseIndex(m_noiseSettings.domainSettings.noiseTypeName); Graphics.Blit(inputTexture, noiseRT, matNoise, noisePass); //now that we have the noise, put it in a simple image mask operation to get the final result filterMat.SetTexture("_ImageMaskTex", noiseRT); Graphics.Blit(inputTexture, outputTexture, filterMat, (int)m_blendMode); RenderTexture.ReleaseTemporary(noiseRT); filterMat.SetTexture("_ImageMaskTex", null); noiseRT = null; break; case ImageMaskOperation.CollisionMask: RenderTexture.active = currentRT; m_multiTerrainOperation.GetCollisionMask(m_collisionMasks); filterMat.SetTexture("_ImageMaskTex", m_multiTerrainOperation.RTbakedMask); Graphics.Blit(inputTexture, outputTexture, filterMat, (int)m_blendMode); filterMat.SetTexture("_ImageMaskTex", null); break; case ImageMaskOperation.StrengthTransform: Graphics.Blit(inputTexture, outputTexture, filterMat, (int)m_blendMode); break; case ImageMaskOperation.PROHydraulicErosion: #if GAIA_PRO_PRESENT m_multiTerrainOperation.RTheightmap.filterMode = FilterMode.Bilinear; Material erosionMat = new Material(Shader.Find("Hidden/GaiaPro/SimpleHeightBlend")); if (m_Eroder == null) { m_Eroder = new HydraulicEroder(); m_Eroder.OnEnable(); } //ImageProcessing.WriteRenderTexture("D:\\ErosionHeightInput.png", m_multiTerrainOperation.RTheightmap); m_Eroder.inputTextures["Height"] = m_multiTerrainOperation.RTheightmap; Vector2 texelSize = new Vector2(m_multiTerrainOperation.m_originTerrain.terrainData.size.x / m_multiTerrainOperation.m_originTerrain.terrainData.heightmapResolution, m_multiTerrainOperation.m_originTerrain.terrainData.size.z / m_multiTerrainOperation.m_originTerrain.terrainData.heightmapResolution); //apply Erosion settings m_Eroder.m_ErosionSettings.m_SimScale.value = m_erosionSimScale; m_Eroder.m_ErosionSettings.m_HydroTimeDelta.value = m_erosionHydroTimeDelta; m_Eroder.m_ErosionSettings.m_HydroIterations.value = m_erosionHydroIterations; m_Eroder.m_ErosionSettings.m_ThermalTimeDelta = m_erosionThermalTimeDelta; m_Eroder.m_ErosionSettings.m_ThermalIterations = m_erosionThermalIterations; m_Eroder.m_ErosionSettings.m_ThermalReposeAngle = m_erosionThermalReposeAngle; m_Eroder.m_ErosionSettings.m_PrecipRate.value = m_erosionPrecipRate; m_Eroder.m_ErosionSettings.m_EvaporationRate.value = m_erosionEvaporationRate; m_Eroder.m_ErosionSettings.m_FlowRate.value = m_erosionFlowRate; m_Eroder.m_ErosionSettings.m_SedimentCapacity.value = m_erosionSedimentCapacity; m_Eroder.m_ErosionSettings.m_SedimentDepositRate.value = m_erosionSedimentDepositRate; m_Eroder.m_ErosionSettings.m_SedimentDissolveRate.value = m_erosionSedimentDissolveRate; m_Eroder.m_ErosionSettings.m_RiverBankDepositRate.value = m_erosionRiverBankDepositRate; m_Eroder.m_ErosionSettings.m_RiverBankDissolveRate.value = m_erosionRiverBankDissolveRate; m_Eroder.m_ErosionSettings.m_RiverBedDepositRate.value = m_erosionRiverBedDepositRate; m_Eroder.m_ErosionSettings.m_RiverBedDissolveRate.value = m_erosionRiverBedDissolveRate; //and erode m_Eroder.ErodeHeightmap(m_multiTerrainOperation.m_originTerrain.terrainData.size, m_multiTerrainOperation.m_terrainDetailBrushTransform.GetBrushXYBounds(), texelSize, false); Vector4 erosionBrushParams = new Vector4(1f, 0.0f, 0.0f, 0.0f); //if (activeLocalFilters) erosionMat.SetTexture("_BrushTex", inputTexture); //else // erosionMat.SetTexture("_BrushTex", localinputTexture); switch (m_erosionMaskOutput) { //case GaiaConstants.ErosionMaskOutput.ErodedSediment: // erosionMat.SetTexture("_NewHeightTex", m_Eroder.outputTextures["Eroded Sediment"]); // break; //case GaiaConstants.ErosionMaskOutput.Height: // erosionMat.SetTexture("_NewHeightTex", m_Eroder.outputTextures["Height"]); // break; case GaiaConstants.ErosionMaskOutput.Sediment: erosionMat.SetTexture("_NewHeightTex", m_Eroder.outputTextures["Sediment"]); break; case GaiaConstants.ErosionMaskOutput.WaterFlux: erosionMat.SetTexture("_NewHeightTex", m_Eroder.outputTextures["Water Flux"]); break; //case GaiaConstants.ErosionMaskOutput.WaterLevel: // erosionMat.SetTexture("_NewHeightTex", m_Eroder.outputTextures["Water Level"]); // break; case GaiaConstants.ErosionMaskOutput.WaterVelocity: erosionMat.SetTexture("_NewHeightTex", m_Eroder.outputTextures["Water Velocity"]); break; } erosionMat.SetVector("_BrushParams", erosionBrushParams); RenderTexture eroderTempRT = RenderTexture.GetTemporary(m_Eroder.outputTextures["Height"].descriptor); m_multiTerrainOperation.SetupMaterialProperties(erosionMat, MultiTerrainOperationType.Heightmap); Graphics.Blit(m_multiTerrainOperation.RTheightmap, eroderTempRT, erosionMat, 0); filterMat.SetTexture("_InputTex", eroderTempRT); Graphics.Blit(eroderTempRT, outputTexture, filterMat, (int)m_blendMode); filterMat.SetTexture("_InputTex", null); erosionMat.SetTexture("_NewHeightTex", null); m_Eroder.ReleaseRenderTextures(); RenderTexture.ReleaseTemporary(eroderTempRT); //ImageProcessing.WriteRenderTexture("D:\\ErosionOutput.png", outputTexture); #else Graphics.Blit(inputTexture, outputTexture); #endif break; case ImageMaskOperation.Smooth: Vector4 brushParams = new Vector4(1f, 0.0f, 0.0f, 0.0f); //ImageProcessing.WriteRenderTexture("D:\\inputTexture.png", inputTexture); filterMat.SetTexture("_MainTex", inputTexture); filterMat.SetTexture("_BrushTex", Texture2D.whiteTexture); filterMat.SetTexture("_HeightTransformTex", strengthTransformCurveTexture); filterMat.SetVector("_BrushParams", brushParams); Vector4 smoothWeights = new Vector4( Mathf.Clamp01(1.0f - Mathf.Abs(m_smoothVerticality)), // centered Mathf.Clamp01(-m_smoothVerticality), // min Mathf.Clamp01(m_smoothVerticality), // max m_smoothBlurRadius); // kernel size filterMat.SetVector("_SmoothWeights", smoothWeights); //Do not set up the UV properties according to the operation. In this case, this would lead to the "smoothness brush" //being rotated inside our existing mask stack. filterMat.SetVector("_PCUVToBrushUVScales", new Vector4(1, 0, 0, 1)); filterMat.SetVector("_PCUVToBrushUVOffset", new Vector4(0, 0, 0.0f, 0.0f)); // Two pass blur (first horizontal, then vertical) //RenderTexture workaround1 = RenderTexture.GetTemporary(m_multiTerrainOperation.RTheightmap.descriptor); RenderTexture tmpsmoothRT = new RenderTexture(m_multiTerrainOperation.RTheightmap.descriptor); //tmpsmoothRT = RenderTexture.GetTemporary(m_multiTerrainOperation.RTheightmap.descriptor); Graphics.Blit(inputTexture, tmpsmoothRT, filterMat, 0); Graphics.Blit(tmpsmoothRT, outputTexture, filterMat, 1); filterMat.SetTexture("_MainTex", null); filterMat.SetTexture("_BrushTex", null); filterMat.SetTexture("_HeightTransformTex", null); tmpsmoothRT.Release(); GameObject.DestroyImmediate(tmpsmoothRT); //RenderTexture.ReleaseTemporary(tmpsmoothRT); //RenderTexture.ReleaseTemporary(workaround1); break; case ImageMaskOperation.TerrainTexture: SpawnRule sr = m_allTextureSpawnRules.FirstOrDefault(x => x.GUID == m_textureMaskSpawnRuleGUID); if (sr != null) { Spawner spawner = m_allTextureSpawners.FirstOrDefault(x => x.m_settings.m_spawnerRules.Contains(sr)); if (spawner != null) { ResourceProtoTexture proto = spawner.m_settings.m_resources.m_texturePrototypes[sr.m_resourceIdx]; TerrainLayer layer = TerrainHelper.GetLayerFromPrototype(proto); m_multiTerrainOperation.GetSplatmap(layer); filterMat.SetTexture("_ImageMaskTex", m_multiTerrainOperation.RTtextureSplatmap); Graphics.Blit(inputTexture, outputTexture, filterMat, (int)m_blendMode); filterMat.SetTexture("_ImageMaskTex", null); } } break; case ImageMaskOperation.PROConcaveConvex: #if GAIA_PRO_PRESENT m_concavityShader = (ComputeShader)(Resources.Load("GaiaConcavity")); Graphics.Blit(inputTexture, outputTexture, filterMat, (int)m_blendMode); int kidx = m_concavityShader.FindKernel("ConcavityMultiply"); switch (m_blendMode) { case ImageMaskBlendMode.GreaterThan: kidx = m_concavityShader.FindKernel("ConcavityGreaterThan"); break; case ImageMaskBlendMode.SmallerThan: kidx = m_concavityShader.FindKernel("ConcavitySmallerThan"); break; case ImageMaskBlendMode.Add: kidx = m_concavityShader.FindKernel("ConcavityAdd"); break; case ImageMaskBlendMode.Subtract: kidx = m_concavityShader.FindKernel("ConcavitySubtract"); break; } m_concavityShader.SetTexture(kidx, "In_BaseMaskTex", inputTexture); //ImageProcessing.WriteRenderTexture("D:\\tempOutput.png", m_multiTerrainOperation.RTheightmap); m_concavityShader.SetTexture(kidx, "In_HeightTex", m_multiTerrainOperation.RTheightmap); m_concavityShader.SetTexture(kidx, "In_HeightTransformTex", strengthTransformCurveTexture); m_concavityShader.SetInt("HeightTransformTexResolution", strengthTransformCurveTexture.width - 1); m_concavityShader.SetTexture(kidx, "OutputTex", outputTexture); //cs.SetTexture(kidx, "RemapTex", remapTex); m_concavityShader.SetVector("HeightmapResolution", new Vector2(m_multiTerrainOperation.RTheightmap.width, m_multiTerrainOperation.RTheightmap.height)); m_concavityShader.SetVector("TextureResolution", new Vector4(inputTexture.width, inputTexture.height, m_concavityFeatureSize, m_concavity)); m_concavityShader.Dispatch(kidx, outputTexture.width, outputTexture.height, 1); //Workaround - the output texture of the compute shader can turn up empty sometimes when used directly in the image mask afterwards //filterMat.SetTexture("_InputTexture", concavityTemp2); //filterMat.SetTexture("_ImageMaskTex", inputTexture); //ImageProcessing.WriteRenderTexture("D:\\tempOutput.png", outputTexture); //Graphics.Blit(concavityTemp2, outputTexture);//, filterMat, (int)m_blendMode); //RenderTexture.ReleaseTemporary(concavityTemp1); //RenderTexture.ReleaseTemporary(concavityTemp2); m_concavityShader = null; #else Graphics.Blit(inputTexture, outputTexture); #endif break; case ImageMaskOperation.WorldBiomeMask: //fetch the world biome mask //RenderTexture.active = currentRT; m_multiTerrainOperation.GetWorldBiomeMask(m_selectedWorldBiomeMaskGUID); //ImageProcessing.WriteRenderTexture("D:\\input.png", inputTexture); //ImageProcessing.WriteRenderTexture("D:\\RTcollision.png", m_multiTerrainOperation.RTcollision); filterMat.SetTexture("_ImageMaskTex", m_multiTerrainOperation.RTbakedMask); Graphics.Blit(inputTexture, outputTexture, filterMat, (int)m_blendMode); filterMat.SetTexture("_ImageMaskTex", null); //ImageProcessing.WriteRenderTexture("D:\\output.png", outputTexture); break; default: break; } filterMat.SetTexture("_InputTex", null); filterMat.SetTexture("_HeightTransformTex", null); GameObject.DestroyImmediate(filterMat); //release input texture //if (inputTexture != null) //{ // inputTexture.Release(); // GameObject.DestroyImmediate(inputTexture); // inputTexture = null; //} #endif return outputTexture; } public void ClearEroder() { #if UNITY_EDITOR && GAIA_PRO_PRESENT if (m_Eroder != null) { m_Eroder.ReleaseRenderTextures(); m_Eroder = null; } #endif } private float CalculateScalarHeightRelativeToSeaLevel(float heightValue, float scalarSeaLevel) { if (heightValue < 0.25f) { //The position is below the marked sea level on the slider -> lerp accordingly heightValue = Mathf.Lerp(0f, scalarSeaLevel, Mathf.InverseLerp(0, 0.25f, heightValue)); } else { //The position is above the marked sea level on the slider -> lerp accordingly heightValue = Mathf.Lerp(scalarSeaLevel, 1f, Mathf.InverseLerp(0.25f, 1f, heightValue)); } return heightValue; } /// /// sets up the default linear strenght transform curve that maps the input 1:1 to the output (equals no transformation at all) /// /// The maximum value at which the curve ends. /// public static AnimationCurve NewAnimCurveStraightUpwards(float max = 1f) { return new AnimationCurve(new Keyframe[2] { new Keyframe() { inTangent = 1, inWeight = 0, outTangent = 1, outWeight = 0, time = 0, value = 0, weightedMode = WeightedMode.None }, new Keyframe() { inTangent = 1, inWeight = 0, outTangent = 1, outWeight = 0, time = 1, value = max, weightedMode = WeightedMode.None } }); ; } /// /// Checks if the current strenght transform curve is still the default linear curve set at initialization /// /// true if original curve, false if the user altered it private bool IsDefaultStrenghtCurve() { //Get a default anim curve for comparison AnimationCurve defaultCurve = NewAnimCurveStraightUpwards(); //different number of keys? => it is a different curve if (m_strengthTransformCurve.keys.Length != defaultCurve.keys.Length) { return false; } //keyframe data different from original? => it is a different curve for (int i = 0; i < m_strengthTransformCurve.keys.Length; i++) { Keyframe current = m_strengthTransformCurve.keys[i]; Keyframe original = defaultCurve.keys[i]; if (current.inTangent != original.inTangent || current.inWeight != original.inWeight || current.outTangent != original.outTangent || current.outWeight != original.outWeight || current.time != original.time || current.value != original.value || current.weightedMode != original.weightedMode) { return false; } } return true; } /// /// Sets up a distance map curve suitable for the "water border" style for the base map creation in the random terrain generator. /// public static AnimationCurve NewAnimCurveWaterBorder() { AnimationCurve returnCurve = new AnimationCurve(new Keyframe[2] { new Keyframe() { inTangent = 0, inWeight = 0, outTangent = 0, outWeight = 0, time = 0, value = 1, weightedMode = WeightedMode.None }, new Keyframe() { inTangent = -3.183739f, inWeight = 0.02412868f, outTangent = -3.183739f, outWeight = 0, time = 1f, value = 0, weightedMode = WeightedMode.None } }); ; return returnCurve; } /// /// Sets up a distance map curve suitable for the "mountain border" style for the base map creation in the random terrain generator. /// public static AnimationCurve NewAnimCurveMountainBorderDistance() { AnimationCurve returnCurve = new AnimationCurve(new Keyframe[2] { new Keyframe() { inTangent = 0, inWeight = 0, outTangent = 0, outWeight = 0, time = 0, value = 0.5f, weightedMode = WeightedMode.None }, new Keyframe() { inTangent = 1.301094f, inWeight = 0.04557639f, outTangent = 1.301094f, outWeight = 0, time = 1f, value = 1f, weightedMode = WeightedMode.None }, }); ; return returnCurve; } public static AnimationCurve NewAnimCurveMountainBorderStrength() { AnimationCurve returnCurve = new AnimationCurve(new Keyframe[3] { new Keyframe() { inTangent = 1.102063f, inWeight = 0, outTangent = 1.102063f, outWeight = 0.2820168f, time = 0, value = 0f, weightedMode = WeightedMode.None }, new Keyframe() { inTangent = 2.96729f, inWeight = 0.08592079f, outTangent = 2.96729f, outWeight = 0.2352394f, time = 0.6f, value = 1f, weightedMode = WeightedMode.None }, new Keyframe() { inTangent = 2.606707f, inWeight = 0.1011895f, outTangent = 2.606707f, outWeight = 0f, time = 1f, value = 2f, weightedMode = WeightedMode.None }, }); ; return returnCurve; } /// /// Sets up a better strenght curve for noise that has a steeper cutoff in strength. /// This curve is better suited to get small islands or patches of noise. /// /// How far from the center of the curve the cutoff should take place. The smaller this value is, the "sharper" the cutoff will be for the noise pattern /// public static AnimationCurve NewAnimCurveDefaultNoise(float distanceFromCenter = 0.2f) { AnimationCurve returnCurve = new AnimationCurve(new Keyframe[4] { new Keyframe() { inTangent = 0, inWeight = 0, outTangent = 0, outWeight = 0, time = 0, value = 0, weightedMode = WeightedMode.None }, new Keyframe() { inTangent = 0, inWeight = 0f, outTangent = 2.5f, outWeight = 0.3333333f, time = 0.5f - distanceFromCenter, value = 0, weightedMode = WeightedMode.None }, new Keyframe() { inTangent = 2.5f, inWeight = 0f, outTangent = 0, outWeight = 0.3333333f, time = 0.5f + distanceFromCenter, value = 1, weightedMode = WeightedMode.None }, new Keyframe() { inTangent = 0, inWeight = 0, outTangent = 0, outWeight = 0, time = 1, value = 1, weightedMode = WeightedMode.None } }); ; return returnCurve; } private Material GetCurrentFXFilterMaterial() { string shaderName = ""; switch (m_operation) { case ImageMaskOperation.ImageMask: shaderName = "Hidden/Gaia/FilterImageMask"; break; case ImageMaskOperation.DistanceMask: shaderName = "Hidden/Gaia/FilterDistanceMask"; break; case ImageMaskOperation.HeightMask: shaderName = "Hidden/Gaia/FilterHeightMask"; break; case ImageMaskOperation.SlopeMask: shaderName = "Hidden/Gaia/FilterSlopeMask"; break; case ImageMaskOperation.NoiseMask: //We need the FilterImageMask as material for the final operation AFTER the noise has been calculated //For the shader that creates the noise itself, see the implementation of the noise mask operation //in Apply() shaderName = "Hidden/Gaia/FilterImageMask"; break; case ImageMaskOperation.CollisionMask: //We need the FilterImageMask as material for the final operation AFTER the Collision mask has been gathered from the operation. //For collection & assembly of the collision mask data, see the implementation of the collision mask operation //in Apply() shaderName = "Hidden/Gaia/FilterImageMask"; break; case ImageMaskOperation.StrengthTransform: shaderName = "Hidden/GaiaPro/StrengthTransform"; break; case ImageMaskOperation.PROHydraulicErosion: //We need the Strength Transform as material for the final operation AFTER the Erosion mask has been gathered from the Eroder. //For collection & assembly of the collision mask data, see the implementation of the erosion mask operation //in Apply() shaderName = "Hidden/GaiaPro/StrengthTransform"; break; case ImageMaskOperation.Smooth: shaderName = "Hidden/Gaia/SmoothHeight"; break; case ImageMaskOperation.TerrainTexture: //Here we just load the splatmap input into an image mask shaderName = "Hidden/Gaia/FilterImageMask"; break; case ImageMaskOperation.PROConcaveConvex: //Concave / Convex is calculated in compute shader, we use the strength transform as a workaround in this case shaderName = "Hidden/GaiaPro/StrengthTransform"; break; case ImageMaskOperation.WorldBiomeMask: //We need the FilterImageMask as material for the final operation AFTER the World Biome mask has been gathered from the operation. //For collection & assembly of the world biome mask data, see the implementation of the world biome mask operation //in Apply() shaderName = "Hidden/Gaia/FilterImageMask"; break; default: break; } if (shaderName == "") { return null; } return new Material(Shader.Find(shaderName)); } public static void CheckMaskStackForInvalidTextureRules(string objectDescription, string objectName, ImageMask[] maskStack) { if (ImageMask.m_allTextureSpawners == null || ImageMask.m_allTextureSpawnRules == null) { ImageMask.RefreshSpawnRuleGUIDs(); } foreach (ImageMask mask in maskStack) { //Is this a texture mask? If yes, check if the associated texture spawn rule exists, if not, put out a warning if (mask.m_operation == ImageMaskOperation.TerrainTexture) { //only perform the check if there is actually a GUID selected in the mask if (!string.IsNullOrEmpty(mask.m_textureMaskSpawnRuleGUID)) { SpawnRule sr = ImageMask.m_allTextureSpawnRules.FirstOrDefault(x => x.GUID == mask.m_textureMaskSpawnRuleGUID); if (sr == null) { mask.m_active = false; mask.m_hasErrors = true; Debug.LogWarning("The " + objectDescription + " '" + objectName + "' uses a Texture Mask that links to a non-existent texture spawn rule. The spawn might not work as intended. Please select the spawner, and assign the correct texture in the texture mask, or remove the texture mask altogether."); } else { mask.m_hasErrors = false; } } else { mask.m_hasErrors = false; } } } } public static void RefreshSpawnRuleGUIDs() { List tempTextureSpawnRules = new List(); List tempTextureSpawner = new List(); List tempTextureSpawnRuleNames = new List(); List tempTreeSpawnRules = new List(); List tempTreeSpawner = new List(); List tempTreeSpawnRuleNames = new List(); Spawner[] allSpawner = Resources.FindObjectsOfTypeAll(); foreach (Spawner spawner in allSpawner) { foreach (SpawnRule sr in spawner.m_settings.m_spawnerRules) { if (sr.m_resourceType == GaiaConstants.SpawnerResourceType.TerrainTexture) { tempTextureSpawnRules.Add(sr); tempTextureSpawnRuleNames.Add(sr.m_name); if (!tempTextureSpawner.Contains(spawner)) { tempTextureSpawner.Add(spawner); } } if (sr.m_resourceType == GaiaConstants.SpawnerResourceType.TerrainTree) { tempTreeSpawnRules.Add(sr); tempTreeSpawnRuleNames.Add(sr.m_name); if (!tempTreeSpawner.Contains(spawner)) { tempTreeSpawner.Add(spawner); } } } } m_allTextureSpawnRuleIndices = Enumerable .Repeat(0, (int)((tempTextureSpawnRules.Count - 0) / 1) + 1) .Select((tr, ti) => tr + (1 * ti)) .ToArray(); CollisionMask.m_allTreeSpawnRuleIndices = Enumerable .Repeat(0, (int)((tempTreeSpawnRules.Count - 0) / 1) + 1) .Select((tr, ti) => tr + (1 * ti)) .ToArray(); CollisionMask.m_allTreeSpawnRules = tempTreeSpawnRules.ToArray(); CollisionMask.m_allTreeSpawners = tempTreeSpawner.ToArray(); CollisionMask.m_allTreeSpawnRuleNames = tempTreeSpawnRuleNames.ToArray(); m_allTextureSpawnRules = tempTextureSpawnRules.ToArray(); m_allTextureSpawners = tempTextureSpawner.ToArray(); m_allTextureSpawnRuleNames = tempTextureSpawnRuleNames.ToArray(); } public static ImageMask Clone(ImageMask source) { ImageMask target = new ImageMask(); #if UNITY_EDITOR GaiaUtils.CopyFields(source, target); //special treatment for the heightmask min max fields - those are properites which will not be copied by the field copy above target.m_absoluteHeightMax = source.m_absoluteHeightMax; target.m_absoluteHeightMin = source.m_absoluteHeightMin; //special treatment for all object fields target.m_distanceMaskCurve = new AnimationCurve(source.m_distanceMaskCurve.keys); target.m_heightMaskCurve = new AnimationCurve(source.m_heightMaskCurve.keys); target.m_slopeMaskCurve = new AnimationCurve(source.m_slopeMaskCurve.keys); target.m_strengthTransformCurve = new AnimationCurve(source.m_strengthTransformCurve.keys); if (source.m_gaiaNoiseSettings != null) { target.m_gaiaNoiseSettings = new GaiaNoiseSettings(); GaiaUtils.CopyFields(source.m_gaiaNoiseSettings, target.m_gaiaNoiseSettings); } if (source.m_noiseSettings != null) { target.m_noiseSettings = (NoiseSettings)ScriptableObject.CreateInstance(typeof(NoiseSettings)); GaiaUtils.CopyFields(source.m_noiseSettings, target.m_noiseSettings); } target.m_noiseToolSettings = new NoiseToolSettings(); GaiaUtils.CopyFields(source.m_noiseToolSettings, target.m_noiseToolSettings); target.noiseSettingsGUI = null; target.m_collisionMasks = new CollisionMask[source.m_collisionMasks.Length]; //Clone all collision masks as well for (int i = 0; i < target.m_collisionMasks.Length; i++) { target.m_collisionMasks[i] = new CollisionMask(); GaiaUtils.CopyFields(source.m_collisionMasks[i], target.m_collisionMasks[i]); } //GaiaUtils.CopyFields(source.m_distanceMaskCurve, target.m_distanceMaskCurve); #endif return target; } } }