text:
/*
---------------------- Unity Terrain Toolkit ----------------------
--
-- Unity Summer of Code 2009
-- Terrain Toolkit for Unity (Version 1.0.1)
-- All code by Sándor Moldán, except where noted.
--
-- Contains an implementation of Perlin noise by Daniel Greenheck.
-- Contains an implementation of the Diamond-Square algorithm by Jim George.
--
-- TerrainToolkit.cs
--
-------------------------------------------------------------------
*/

using UnityEngine;
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;

namespace UnityLibrary
{

    enum FeatureType { Mountains = 0, Hills = 1, Plateaus = 2 }

    [ExecuteInEditMode()]
    [AddComponentMenu("Terrain/Terrain Toolkit")]
    public class TerrainToolkit : MonoBehaviour
    {

        // -------------------------------------------------------------------------------------------------------- VARS

        // Delegates...
        public delegate void ErosionProgressDelegate(string titleString, string displayString, int iteration, int nIterations, float percentComplete);
        public delegate void TextureProgressDelegate(string titleString, string displayString, float percentComplete);
        public delegate void GeneratorProgressDelegate(string titleString, string displayString, float percentComplete);

        // GUI...
        public GUISkin guiSkin;
        public Texture2D createIcon;
        public Texture2D erodeIcon;
        public Texture2D textureIcon;
        public Texture2D mooreIcon;
        public Texture2D vonNeumannIcon;
        public Texture2D mountainsIcon;
        public Texture2D hillsIcon;
        public Texture2D plateausIcon;

        // Textures...
        public Texture2D defaultTexture;

        // Global..
        public int toolModeInt = 0;
        private ErosionMode erosionMode = ErosionMode.Filter;
        private ErosionType erosionType = ErosionType.Thermal;
        public int erosionTypeInt = 0;
        private GeneratorType generatorType = GeneratorType.Voronoi;
        public int generatorTypeInt = 0;
        public bool isBrushOn = false;
        public bool isBrushHidden = false;
        public bool isBrushPainting = false;
        public Vector3 brushPosition;
        public float brushSize = 50.0f;
        public float brushOpacity = 1.0f;
        public float brushSoftness = 0.5f;

        // Settings...
        public int neighbourhoodInt = 0;
        private Neighbourhood neighbourhood = Neighbourhood.Moore;
        public bool useDifferenceMaps = true;

        // Thermal...
        public int thermalIterations = 25;
        public float thermalMinSlope = 1.0f;
        public float thermalFalloff = 0.5f;

        // Hydraulic...
        public int hydraulicTypeInt = 0;
        public HydraulicType hydraulicType = HydraulicType.Fast;
        public int hydraulicIterations = 25;
        // Fast...
        public float hydraulicMaxSlope = 60.0f;
        public float hydraulicFalloff = 0.5f;
        // Full...
        public float hydraulicRainfall = 0.01f;
        public float hydraulicEvaporation = 0.5f;
        public float hydraulicSedimentSolubility = 0.01f;
        public float hydraulicSedimentSaturation = 0.1f;
        // Velocity...
        public float hydraulicVelocityRainfall = 0.01f;
        public float hydraulicVelocityEvaporation = 0.5f;
        public float hydraulicVelocitySedimentSolubility = 0.01f;
        public float hydraulicVelocitySedimentSaturation = 0.1f;
        public float hydraulicVelocity = 20.0f;
        public float hydraulicMomentum = 1.0f;
        public float hydraulicEntropy = 0.0f;
        public float hydraulicDowncutting = 0.1f;

        // Tidal...
        public int tidalIterations = 25;
        public float tidalSeaLevel = 50.0f;
        public float tidalRangeAmount = 5.0f;
        public float tidalCliffLimit = 60.0f;

        // Wind...
        public int windIterations = 25;
        public float windDirection = 0.0f;
        public float windForce = 0.5f;
        public float windLift = 0.01f;
        public float windGravity = 0.5f;
        public float windCapacity = 0.01f;
        public float windEntropy = 0.1f;
        public float windSmoothing = 0.25f;

        // Texturing...
        public SplatPrototype[] splatPrototypes;
        public Texture2D tempTexture;
        public float slopeBlendMinAngle = 60.0f;
        public float slopeBlendMaxAngle = 75.0f;
        public List<float> heightBlendPoints;
        public string[] gradientStyles;

        // Generators...
        public int voronoiTypeInt = 0;
        public VoronoiType voronoiType = VoronoiType.Linear;
        public int voronoiCells = 16;
        public float voronoiFeatures = 1.0f;
        public float voronoiScale = 1.0f;
        public float voronoiBlend = 1.0f;
        public float diamondSquareDelta = 0.5f;
        public float diamondSquareBlend = 1.0f;
        public int perlinFrequency = 4;
        public float perlinAmplitude = 1.0f;
        public int perlinOctaves = 8;
        public float perlinBlend = 1.0f;
        public float smoothBlend = 1.0f;
        public int smoothIterations;
        public float normaliseMin = 0.0f;
        public float normaliseMax = 1.0f;
        public float normaliseBlend = 1.0f;

        public class PeakDistance : IComparable
        {
            public int id;
            public float dist;

            public int CompareTo(object obj)
            {
                PeakDistance Compare = (PeakDistance)obj;
                int result = this.dist.CompareTo(Compare.dist);
                if (result == 0)
                {
                    result = this.dist.CompareTo(Compare.dist);
                }
                return result;
            }
        }

        public struct Peak
        {
            public Vector2 peakPoint;
            public float peakHeight;
        }

        // -------------------------------------------------------------------------------------------------------- PRESET CLASSES

        public class voronoiPresetData
        {
            public string presetName;
            public VoronoiType voronoiType;
            public int voronoiCells;
            public float voronoiFeatures;
            public float voronoiScale;
            public float voronoiBlend;

            public voronoiPresetData(string pn, VoronoiType vt, int c, float vf, float vs, float vb)
            {
                presetName = pn;
                voronoiType = vt;
                voronoiCells = c;
                voronoiFeatures = vf;
                voronoiScale = vs;
                voronoiBlend = vb;
            }
        }

        public class fractalPresetData
        {
            public string presetName;
            public float diamondSquareDelta;
            public float diamondSquareBlend;

            public fractalPresetData(string pn, float dsd, float dsb)
            {
                presetName = pn;
                diamondSquareDelta = dsd;
                diamondSquareBlend = dsb;
            }
        }

        public class perlinPresetData
        {
            public string presetName;
            public int perlinFrequency;
            public float perlinAmplitude;
            public int perlinOctaves;
            public float perlinBlend;

            public perlinPresetData(string pn, int pf, float pa, int po, float pb)
            {
                presetName = pn;
                perlinFrequency = pf;
                perlinAmplitude = pa;
                perlinOctaves = po;
                perlinBlend = pb;
            }
        }

        public class thermalErosionPresetData
        {
            public string presetName;
            public int thermalIterations;
            public float thermalMinSlope;
            public float thermalFalloff;

            public thermalErosionPresetData(string pn, int ti, float tms, float tba)
            {
                presetName = pn;
                thermalIterations = ti;
                thermalMinSlope = tms;
                thermalFalloff = tba;
            }
        }

        public class fastHydraulicErosionPresetData
        {
            public string presetName;
            public int hydraulicIterations;
            public float hydraulicMaxSlope;
            public float hydraulicFalloff;

            public fastHydraulicErosionPresetData(string pn, int hi, float hms, float hba)
            {
                presetName = pn;
                hydraulicIterations = hi;
                hydraulicMaxSlope = hms;
                hydraulicFalloff = hba;
            }
        }

        public class fullHydraulicErosionPresetData
        {
            public string presetName;
            public int hydraulicIterations;
            public float hydraulicRainfall;
            public float hydraulicEvaporation;
            public float hydraulicSedimentSolubility;
            public float hydraulicSedimentSaturation;

            public fullHydraulicErosionPresetData(string pn, int hi, float hr, float he, float hso, float hsa)
            {
                presetName = pn;
                hydraulicIterations = hi;
                hydraulicRainfall = hr;
                hydraulicEvaporation = he;
                hydraulicSedimentSolubility = hso;
                hydraulicSedimentSaturation = hsa;
            }
        }

        public class velocityHydraulicErosionPresetData
        {
            public string presetName;
            public int hydraulicIterations;
            public float hydraulicVelocityRainfall;
            public float hydraulicVelocityEvaporation;
            public float hydraulicVelocitySedimentSolubility;
            public float hydraulicVelocitySedimentSaturation;
            public float hydraulicVelocity;
            public float hydraulicMomentum;
            public float hydraulicEntropy;
            public float hydraulicDowncutting;

            public velocityHydraulicErosionPresetData(string pn, int hi, float hvr, float hve, float hso, float hsa, float hv, float hm, float he, float hd)
            {
                presetName = pn;
                hydraulicIterations = hi;
                hydraulicVelocityRainfall = hvr;
                hydraulicVelocityEvaporation = hve;
                hydraulicVelocitySedimentSolubility = hso;
                hydraulicVelocitySedimentSaturation = hsa;
                hydraulicVelocity = hv;
                hydraulicMomentum = hm;
                hydraulicEntropy = he;
                hydraulicDowncutting = hd;
            }
        }

        public class tidalErosionPresetData
        {
            public string presetName;
            public int tidalIterations;
            public float tidalRangeAmount;
            public float tidalCliffLimit;

            public tidalErosionPresetData(string pn, int ti, float tra, float tcl)
            {
                presetName = pn;
                tidalIterations = ti;
                tidalRangeAmount = tra;
                tidalCliffLimit = tcl;
            }
        }

        public class windErosionPresetData
        {
            public string presetName;
            public int windIterations;
            public float windDirection;
            public float windForce;
            public float windLift;
            public float windGravity;
            public float windCapacity;
            public float windEntropy;
            public float windSmoothing;

            public windErosionPresetData(string pn, int wi, float wd, float wf, float wl, float wg, float wc, float we, float ws)
            {
                presetName = pn;
                windIterations = wi;
                windDirection = wd;
                windForce = wf;
                windLift = wl;
                windGravity = wg;
                windCapacity = wc;
                windEntropy = we;
                windSmoothing = ws;
            }
        }

        // -------------------------------------------------------------------------------------------------------- PRESETS

        [System.NonSerialized]
        public bool presetsInitialised = false;
        [System.NonSerialized]
        public int voronoiPresetId = 0;
        [System.NonSerialized]
        public int fractalPresetId = 0;
        [System.NonSerialized]
        public int perlinPresetId = 0;
        [System.NonSerialized]
        public int thermalErosionPresetId = 0;
        [System.NonSerialized]
        public int fastHydraulicErosionPresetId = 0;
        [System.NonSerialized]
        public int fullHydraulicErosionPresetId = 0;
        [System.NonSerialized]
        public int velocityHydraulicErosionPresetId = 0;
        [System.NonSerialized]
        public int tidalErosionPresetId = 0;
        [System.NonSerialized]
        public int windErosionPresetId = 0;

        public ArrayList voronoiPresets = new ArrayList();
        public ArrayList fractalPresets = new ArrayList();
        public ArrayList perlinPresets = new ArrayList();
        public ArrayList thermalErosionPresets = new ArrayList();
        public ArrayList fastHydraulicErosionPresets = new ArrayList();
        public ArrayList fullHydraulicErosionPresets = new ArrayList();
        public ArrayList velocityHydraulicErosionPresets = new ArrayList();
        public ArrayList tidalErosionPresets = new ArrayList();
        public ArrayList windErosionPresets = new ArrayList();

        public void addPresets()
        {
            presetsInitialised = true;
            voronoiPresets = new ArrayList();
            fractalPresets = new ArrayList();
            perlinPresets = new ArrayList();
            thermalErosionPresets = new ArrayList();
            fastHydraulicErosionPresets = new ArrayList();
            fullHydraulicErosionPresets = new ArrayList();
            velocityHydraulicErosionPresets = new ArrayList();
            tidalErosionPresets = new ArrayList();
            windErosionPresets = new ArrayList();
            voronoiPresets.Add(new voronoiPresetData("Scattered Peaks", VoronoiType.Linear, 16, 8, 0.5f, 1.0f));
            voronoiPresets.Add(new voronoiPresetData("Rolling Hills", VoronoiType.Sine, 8, 8, 0.0f, 1.0f));
            voronoiPresets.Add(new voronoiPresetData("Jagged Mountains", VoronoiType.Linear, 32, 32, 0.5f, 1.0f));
            fractalPresets.Add(new fractalPresetData("Rolling Plains", 0.4f, 1.0f));
            fractalPresets.Add(new fractalPresetData("Rough Mountains", 0.5f, 1.0f));
            fractalPresets.Add(new fractalPresetData("Add Noise", 0.75f, 0.05f));
            perlinPresets.Add(new perlinPresetData("Rough Plains", 2, 0.5f, 9, 1.0f));
            perlinPresets.Add(new perlinPresetData("Rolling Hills", 5, 0.75f, 3, 1.0f));
            perlinPresets.Add(new perlinPresetData("Rocky Mountains", 4, 1.0f, 8, 1.0f));
            perlinPresets.Add(new perlinPresetData("Hellish Landscape", 11, 1.0f, 7, 1.0f));
            perlinPresets.Add(new perlinPresetData("Add Noise", 10, 1.0f, 8, 0.2f));
            thermalErosionPresets.Add(new thermalErosionPresetData("Gradual, Weak Erosion", 25, 7.5f, 0.5f));
            thermalErosionPresets.Add(new thermalErosionPresetData("Fast, Harsh Erosion", 25, 2.5f, 0.1f));
            thermalErosionPresets.Add(new thermalErosionPresetData("Thermal Erosion Brush", 25, 0.1f, 0.0f));
            fastHydraulicErosionPresets.Add(new fastHydraulicErosionPresetData("Rainswept Earth", 25, 70.0f, 1.0f));
            fastHydraulicErosionPresets.Add(new fastHydraulicErosionPresetData("Terraced Slopes", 25, 30.0f, 0.4f));
            fastHydraulicErosionPresets.Add(new fastHydraulicErosionPresetData("Hydraulic Erosion Brush", 25, 85.0f, 1.0f));
            fullHydraulicErosionPresets.Add(new fullHydraulicErosionPresetData("Low Rainfall, Hard Rock", 25, 0.01f, 0.5f, 0.01f, 0.1f));
            fullHydraulicErosionPresets.Add(new fullHydraulicErosionPresetData("Low Rainfall, Soft Earth", 25, 0.01f, 0.5f, 0.06f, 0.15f));
            fullHydraulicErosionPresets.Add(new fullHydraulicErosionPresetData("Heavy Rainfall, Hard Rock", 25, 0.02f, 0.5f, 0.01f, 0.1f));
            fullHydraulicErosionPresets.Add(new fullHydraulicErosionPresetData("Heavy Rainfall, Soft Earth", 25, 0.02f, 0.5f, 0.06f, 0.15f));
            velocityHydraulicErosionPresets.Add(new velocityHydraulicErosionPresetData("Low Rainfall, Hard Rock", 25, 0.01f, 0.5f, 0.01f, 0.1f, 1.0f, 1.0f, 0.05f, 0.12f));
            velocityHydraulicErosionPresets.Add(new velocityHydraulicErosionPresetData("Low Rainfall, Soft Earth", 25, 0.01f, 0.5f, 0.06f, 0.15f, 1.2f, 2.8f, 0.05f, 0.12f));
            velocityHydraulicErosionPresets.Add(new velocityHydraulicErosionPresetData("Heavy Rainfall, Hard Rock", 25, 0.02f, 0.5f, 0.01f, 0.1f, 1.1f, 2.2f, 0.05f, 0.12f));
            velocityHydraulicErosionPresets.Add(new velocityHydraulicErosionPresetData("Heavy Rainfall, Soft Earth", 25, 0.02f, 0.5f, 0.06f, 0.15f, 1.2f, 2.4f, 0.05f, 0.12f));
            velocityHydraulicErosionPresets.Add(new velocityHydraulicErosionPresetData("Carved Stone", 25, 0.01f, 0.5f, 0.01f, 0.1f, 2.0f, 1.25f, 0.05f, 0.35f));
            tidalErosionPresets.Add(new tidalErosionPresetData("Low Tidal Range, Calm Waves", 25, 5.0f, 65.0f));
            tidalErosionPresets.Add(new tidalErosionPresetData("Low Tidal Range, Strong Waves", 25, 5.0f, 35.0f));
            tidalErosionPresets.Add(new tidalErosionPresetData("High Tidal Range, Calm Water", 25, 15.0f, 55.0f));
            tidalErosionPresets.Add(new tidalErosionPresetData("High Tidal Range, Strong Waves", 25, 15.0f, 25.0f));
            windErosionPresets.Add(new windErosionPresetData("Default (Northerly)", 25, 180.0f, 0.5f, 0.01f, 0.5f, 0.01f, 0.1f, 0.25f));
            windErosionPresets.Add(new windErosionPresetData("Default (Southerly)", 25, 0.0f, 0.5f, 0.01f, 0.5f, 0.01f, 0.1f, 0.25f));
            windErosionPresets.Add(new windErosionPresetData("Default (Easterly)", 25, 270.0f, 0.5f, 0.01f, 0.5f, 0.01f, 0.1f, 0.25f));
            windErosionPresets.Add(new windErosionPresetData("Default (Westerly)", 25, 90.0f, 0.5f, 0.01f, 0.5f, 0.01f, 0.1f, 0.25f));
        }

        public void setVoronoiPreset(voronoiPresetData preset)
        {
            generatorTypeInt = 0;
            generatorType = GeneratorType.Voronoi;
            voronoiTypeInt = (int)preset.voronoiType;
            voronoiType = preset.voronoiType;
            voronoiCells = preset.voronoiCells;
            voronoiFeatures = preset.voronoiFeatures;
            voronoiScale = preset.voronoiScale;
            voronoiBlend = preset.voronoiBlend;
        }

        public void setFractalPreset(fractalPresetData preset)
        {
            generatorTypeInt = 1;
            generatorType = GeneratorType.DiamondSquare;
            diamondSquareDelta = preset.diamondSquareDelta;
            diamondSquareBlend = preset.diamondSquareBlend;
        }

        public void setPerlinPreset(perlinPresetData preset)
        {
            generatorTypeInt = 2;
            generatorType = GeneratorType.Perlin;
            perlinFrequency = preset.perlinFrequency;
            perlinAmplitude = preset.perlinAmplitude;
            perlinOctaves = preset.perlinOctaves;
            perlinBlend = preset.perlinBlend;
        }

        public void setThermalErosionPreset(thermalErosionPresetData preset)
        {
            erosionTypeInt = 0;
            erosionType = ErosionType.Thermal;
            thermalIterations = preset.thermalIterations;
            thermalMinSlope = preset.thermalMinSlope;
            thermalFalloff = preset.thermalFalloff;
        }

        public void setFastHydraulicErosionPreset(fastHydraulicErosionPresetData preset)
        {
            erosionTypeInt = 1;
            erosionType = ErosionType.Hydraulic;
            hydraulicTypeInt = 0;
            hydraulicType = HydraulicType.Fast;
            hydraulicIterations = preset.hydraulicIterations;
            hydraulicMaxSlope = preset.hydraulicMaxSlope;
            hydraulicFalloff = preset.hydraulicFalloff;
        }

        public void setFullHydraulicErosionPreset(fullHydraulicErosionPresetData preset)
        {
            erosionTypeInt = 1;
            erosionType = ErosionType.Hydraulic;
            hydraulicTypeInt = 1;
            hydraulicType = HydraulicType.Full;
            hydraulicIterations = preset.hydraulicIterations;
            hydraulicRainfall = preset.hydraulicRainfall;
            hydraulicEvaporation = preset.hydraulicEvaporation;
            hydraulicSedimentSolubility = preset.hydraulicSedimentSolubility;
            hydraulicSedimentSaturation = preset.hydraulicSedimentSaturation;
        }

        public void setVelocityHydraulicErosionPreset(velocityHydraulicErosionPresetData preset)
        {
            erosionTypeInt = 1;
            erosionType = ErosionType.Hydraulic;
            hydraulicTypeInt = 2;
            hydraulicType = HydraulicType.Velocity;
            hydraulicIterations = preset.hydraulicIterations;
            hydraulicVelocityRainfall = preset.hydraulicVelocityRainfall;
            hydraulicVelocityEvaporation = preset.hydraulicVelocityEvaporation;
            hydraulicVelocitySedimentSolubility = preset.hydraulicVelocitySedimentSolubility;
            hydraulicVelocitySedimentSaturation = preset.hydraulicVelocitySedimentSaturation;
            hydraulicVelocity = preset.hydraulicVelocity;
            hydraulicMomentum = preset.hydraulicMomentum;
            hydraulicEntropy = preset.hydraulicEntropy;
            hydraulicDowncutting = preset.hydraulicDowncutting;
        }

        public void setTidalErosionPreset(tidalErosionPresetData preset)
        {
            erosionTypeInt = 2;
            erosionType = ErosionType.Tidal;
            tidalIterations = preset.tidalIterations;
            tidalRangeAmount = preset.tidalRangeAmount;
            tidalCliffLimit = preset.tidalCliffLimit;
        }

        public void setWindErosionPreset(windErosionPresetData preset)
        {
            erosionTypeInt = 3;
            erosionType = ErosionType.Wind;
            windIterations = preset.windIterations;
            windDirection = preset.windDirection;
            windForce = preset.windForce;
            windLift = preset.windLift;
            windGravity = preset.windGravity;
            windCapacity = preset.windCapacity;
            windEntropy = preset.windEntropy;
            windSmoothing = preset.windSmoothing;
        }

        // -------------------------------------------------------------------------------------------------------- ENUMERATIONS

        public enum ToolMode { Create = 0, Erode = 1, Texture = 2 }
        public enum ErosionMode { Filter = 0, Brush = 1 }
        public enum ErosionType { Thermal = 0, Hydraulic = 1, Tidal = 2, Wind = 3, Glacial = 4 }
        public enum HydraulicType { Fast = 0, Full = 1, Velocity = 2 }
        public enum Neighbourhood { Moore = 0, VonNeumann = 1 }
        public enum GeneratorType { Voronoi = 0, DiamondSquare = 1, Perlin = 2, Smooth = 3, Normalise = 4 }
        public enum VoronoiType { Linear = 0, Sine = 1, Tangent = 2 }
        public enum FeatureType { Mountains = 0, Hills = 1, Plateaus = 2 }

        // -------------------------------------------------------------------------------------------------------- SYSTEM

        public void Update()
        {
            if (isBrushOn)
            {
                if (toolModeInt != 1 || erosionTypeInt > 2 || (erosionTypeInt == 1 && hydraulicTypeInt > 0))
                {
                    isBrushOn = false;
                }
            }
        }

        public void OnDrawGizmos()
        {
            Terrain ter = (Terrain)GetComponent(typeof(Terrain));
            if (ter == null)
            {
                return;
            }
            // Brush gizmos...
            if (isBrushOn && !isBrushHidden)
            {
                if (isBrushPainting)
                {
                    Gizmos.color = Color.red;
                } else
                {
                    Gizmos.color = Color.white;
                }
                float crossHairSize = brushSize / 4.0f;
                Gizmos.DrawLine((brushPosition + new Vector3(-crossHairSize, 0, 0)), (brushPosition + new Vector3(crossHairSize, 0, 0)));
                Gizmos.DrawLine(brushPosition + new Vector3(0, -crossHairSize, 0), brushPosition + new Vector3(0, crossHairSize, 0));
                Gizmos.DrawLine(brushPosition + new Vector3(0, 0, -crossHairSize), brushPosition + new Vector3(0, 0, crossHairSize));
                Gizmos.DrawWireCube(brushPosition, new Vector3(brushSize, 0, brushSize));
                Gizmos.DrawWireSphere(brushPosition, brushSize / 2);
            }
            TerrainData terData = ter.terrainData;
            Vector3 terSize = terData.size;
            // Tidal gizmos...
            if (toolModeInt == 1 && erosionTypeInt == 2)
            {
                Gizmos.color = Color.blue;
                Gizmos.DrawWireCube(new Vector3(transform.position.x + terSize.x / 2, tidalSeaLevel, transform.position.z + terSize.z / 2), new Vector3(terSize.x, 0.0f, terSize.z));
                Gizmos.color = Color.white;
                Gizmos.DrawWireCube(new Vector3(transform.position.x + terSize.x / 2, tidalSeaLevel, transform.position.z + terSize.z / 2), new Vector3(terSize.x, tidalRangeAmount * 2, terSize.z));
            }
            // Wind gizmos...
            if (toolModeInt == 1 && erosionTypeInt == 3)
            {
                Gizmos.color = Color.blue;
                Quaternion windQuaternion = Quaternion.Euler(0.0f, windDirection, 0);
                Vector3 windVector = windQuaternion * Vector3.forward;
                Vector3 startPoint = new Vector3(transform.position.x + terSize.x / 2, transform.position.y + terSize.y, transform.position.z + terSize.z / 2);
                Vector3 endPoint = startPoint + windVector * (terSize.x / 4);
                Vector3 midPoint = startPoint + windVector * (terSize.x / 6);
                Gizmos.DrawLine(startPoint, endPoint);
                Gizmos.DrawLine(endPoint, midPoint + new Vector3(0, terSize.x / 16, 0));
                Gizmos.DrawLine(endPoint, midPoint - new Vector3(0, terSize.x / 16, 0));
            }
        }

        // -------------------------------------------------------------------------------------------------------- METHODS

        public void paint()
        {
            convertIntVarsToEnums();
            erodeTerrainWithBrush();
        }

        private void erodeTerrainWithBrush()
        {
            erosionMode = ErosionMode.Brush;
            // Error checking...
            Terrain ter = (Terrain)GetComponent(typeof(Terrain));
            if (ter == null)
            {
                return;
            }
            int Px = 0;
            int Py = 0;
            try
            {
                TerrainData terData = ter.terrainData;
                int Tw = terData.heightmapWidth;
                int Th = terData.heightmapHeight;
                Vector3 Ts = terData.size;
                int Sx = (int)Mathf.Floor((Tw / Ts.x) * brushSize);
                int Sy = (int)Mathf.Floor((Th / Ts.z) * brushSize);
                Vector3 localBrushPosition = transform.InverseTransformPoint(brushPosition);
                Px = (int)Mathf.Round((localBrushPosition.x / Ts.x) * Tw - (Sx / 2));
                Py = (int)Mathf.Round((localBrushPosition.z / Ts.z) * Th - (Sy / 2));
                if (Px < 0)
                {
                    Sx = Sx + Px;
                    Px = 0;
                }
                if (Py < 0)
                {
                    Sy = Sy + Py;
                    Py = 0;
                }
                if (Px + Sx > Tw)
                {
                    Sx = Tw - Px;
                }
                if (Py + Sy > Th)
                {
                    Sy = Th - Py;
                }
                float[,] heightMap = terData.GetHeights(Px, Py, Sx, Sy);
                Sx = heightMap.GetLength(1);
                Sy = heightMap.GetLength(0);
                // Pass the height array to the erosion script...
                float[,] erodedheightMap = (float[,])heightMap.Clone();
                ErosionProgressDelegate erosionProgressDelegate = new ErosionProgressDelegate(dummyErosionProgress);
                erodedheightMap = fastErosion(erodedheightMap, new Vector2(Sx, Sy), 1, erosionProgressDelegate);
                // Apply it to the terrain object
                float sampleRadius = Sx / 2.0f;
                for (int Ty = 0; Ty < Sx; Ty++)
                {
                    for (int Tx = 0; Tx < Sy; Tx++)
                    {
                        float oldHeightAtPoint = heightMap[Tx, Ty];
                        float newHeightAtPoint = erodedheightMap[Tx, Ty];
                        float distancefromOrigin = Vector2.Distance(new Vector2(Tx, Ty), new Vector2(sampleRadius, sampleRadius));
                        float weightAtPoint = 1.0f - ((distancefromOrigin - (sampleRadius - (sampleRadius * brushSoftness))) / (sampleRadius * brushSoftness));
                        if (weightAtPoint < 0.0f)
                        {
                            weightAtPoint = 0.0f;
                        } else if (weightAtPoint > 1.0f)
                        {
                            weightAtPoint = 1.0f;
                        }
                        weightAtPoint *= brushOpacity;
                        float blendedHeightAtPoint = (newHeightAtPoint * weightAtPoint) + (oldHeightAtPoint * (1.0f - weightAtPoint));
                        heightMap[Tx, Ty] = blendedHeightAtPoint;
                    }
                }
                terData.SetHeights(Px, Py, heightMap);
            }
            catch (Exception e)
            {
                Debug.LogError("A brush error occurred: " + e);
            }
        }

        public void erodeAllTerrain(ErosionProgressDelegate erosionProgressDelegate)
        {
            erosionMode = ErosionMode.Filter;
            // Check enum vars...
            convertIntVarsToEnums();
            // Error checking...
            Terrain ter = (Terrain)GetComponent(typeof(Terrain));
            if (ter == null)
            {
                return;
            }
            try
            {
                TerrainData terData = ter.terrainData;
                int Tw = terData.heightmapWidth;
                int Th = terData.heightmapHeight;
                float[,] heightMap = terData.GetHeights(0, 0, Tw, Th);
                // Set the number of iterations and pass the height array to the appropriate erosion script...
                int iterations;
                switch (erosionType)
                {
                    case ErosionType.Thermal:
                        iterations = thermalIterations;
                        heightMap = fastErosion(heightMap, new Vector2(Tw, Th), iterations, erosionProgressDelegate);
                        break;
                    case ErosionType.Hydraulic:
                        iterations = hydraulicIterations;
                        switch (hydraulicType)
                        {
                            case HydraulicType.Fast:
                                heightMap = fastErosion(heightMap, new Vector2(Tw, Th), iterations, erosionProgressDelegate);
                                break;
                            case HydraulicType.Full:
                                heightMap = fullHydraulicErosion(heightMap, new Vector2(Tw, Th), iterations, erosionProgressDelegate);
                                break;
                            case HydraulicType.Velocity:
                                heightMap = velocityHydraulicErosion(heightMap, new Vector2(Tw, Th), iterations, erosionProgressDelegate);
                                break;
                        }
                        break;
                    case ErosionType.Tidal:
                        Vector3 terSize = terData.size;
                        if (tidalSeaLevel >= transform.position.y && tidalSeaLevel <= transform.position.y + terSize.y)
                        {
                            iterations = tidalIterations;
                            heightMap = fastErosion(heightMap, new Vector2(Tw, Th), iterations, erosionProgressDelegate);
                        } else
                        {
                            Debug.LogError("Sea level does not intersect terrain object. Erosion operation failed.");
                        }
                        break;
                    case ErosionType.Wind:
                        iterations = windIterations;
                        heightMap = windErosion(heightMap, new Vector2(Tw, Th), iterations, erosionProgressDelegate);
                        break;
                    default:
                        return;
                }
                // Apply it to the terrain object
                terData.SetHeights(0, 0, heightMap);
            }
            catch (Exception e)
            {
                Debug.LogError("An error occurred: " + e);
            }
        }

        private float[,] fastErosion(float[,] heightMap, Vector2 arraySize, int iterations, ErosionProgressDelegate erosionProgressDelegate)
        {
            int Tw = (int)arraySize.y;
            int Th = (int)arraySize.x;
            float[,] heightDiffMap = new float[Tw, Th];
            Terrain ter = (Terrain)GetComponent(typeof(Terrain));
            TerrainData terData = ter.terrainData;
            Vector3 terSize = terData.size;
            float minSlopeBlendMin = 0.0f;
            float minSlopeBlendMax = 0.0f;
            float maxSlopeBlendMin = 0.0f;
            float maxSlopeBlendMax = 0.0f;
            float seaLevel = 0.0f;
            float lowerTidalLimit = 0.0f;
            float upperTidalLimit = 0.0f;
            float tidalRange = 0.0f;
            float cliffLimit = 0.0f;
            switch (erosionType)
            {
                case ErosionType.Thermal:
                    minSlopeBlendMin = ((terSize.x / Tw) * Mathf.Tan(thermalMinSlope * Mathf.Deg2Rad)) / terSize.y;
                    if (minSlopeBlendMin > 1.0f)
                    {
                        minSlopeBlendMin = 1.0f;
                    }
                    if (thermalFalloff == 1.0f)
                    {
                        thermalFalloff = 0.999f;
                    }
                    float thermalMaxSlope = thermalMinSlope + ((90 - thermalMinSlope) * thermalFalloff);
                    minSlopeBlendMax = ((terSize.x / Tw) * Mathf.Tan(thermalMaxSlope * Mathf.Deg2Rad)) / terSize.y;
                    if (minSlopeBlendMax > 1.0f)
                    {
                        minSlopeBlendMax = 1.0f;
                    }
                    break;
                case ErosionType.Hydraulic:
                    maxSlopeBlendMax = ((terSize.x / Tw) * Mathf.Tan(hydraulicMaxSlope * Mathf.Deg2Rad)) / terSize.y;
                    if (hydraulicFalloff == 0.0f)
                    {
                        hydraulicFalloff = 0.001f;
                    }
                    float hydraulicMinSlope = hydraulicMaxSlope * (1 - hydraulicFalloff);
                    maxSlopeBlendMin = ((terSize.x / Tw) * Mathf.Tan(hydraulicMinSlope * Mathf.Deg2Rad)) / terSize.y;
                    break;
                case ErosionType.Tidal:
                    seaLevel = (tidalSeaLevel - transform.position.y) / (transform.position.y + terSize.y);
                    lowerTidalLimit = (tidalSeaLevel - transform.position.y - tidalRangeAmount) / (transform.position.y + terSize.y);
                    upperTidalLimit = (tidalSeaLevel - transform.position.y + tidalRangeAmount) / (transform.position.y + terSize.y);
                    tidalRange = upperTidalLimit - seaLevel;
                    cliffLimit = ((terSize.x / Tw) * Mathf.Tan(tidalCliffLimit * Mathf.Deg2Rad)) / terSize.y;
                    break;
                default:
                    return heightMap;
            }
            int xNeighbours;
            int yNeighbours;
            int xShift;
            int yShift;
            int xIndex;
            int yIndex;
            int Tx;
            int Ty;
            // Start iterations...
            for (int iter = 0; iter < iterations; iter++)
            {
                for (Ty = 0; Ty < Th; Ty++)
                {
                    // y...
                    if (Ty == 0)
                    {
                        yNeighbours = 2;
                        yShift = 0;
                        yIndex = 0;
                    } else if (Ty == Th - 1)
                    {
                        yNeighbours = 2;
                        yShift = -1;
                        yIndex = 1;
                    } else
                    {
                        yNeighbours = 3;
                        yShift = -1;
                        yIndex = 1;
                    }
                    for (Tx = 0; Tx < Tw; Tx++)
                    {
                        // x...
                        if (Tx == 0)
                        {
                            xNeighbours = 2;
                            xShift = 0;
                            xIndex = 0;
                        } else if (Tx == Tw - 1)
                        {
                            xNeighbours = 2;
                            xShift = -1;
                            xIndex = 1;
                        } else
                        {
                            xNeighbours = 3;
                            xShift = -1;
                            xIndex = 1;
                        }
                        // Calculate slope...
                        float tMin = 1.0f;
                        float tMax = 0.0f;
                        float tCumulative = 0.0f;
                        int Ny;
                        int Nx;
                        float t;
                        float heightAtIndex = heightMap[Tx + xIndex + xShift, Ty + yIndex + yShift]; // Get height at index
                        float hCumulative = heightAtIndex;
                        int nNeighbours = 0;
                        for (Ny = 0; Ny < yNeighbours; Ny++)
                        {
                            for (Nx = 0; Nx < xNeighbours; Nx++)
                            {
                                if (Nx != xIndex || Ny != yIndex)
                                {
                                    if (neighbourhood == Neighbourhood.Moore || (neighbourhood == Neighbourhood.VonNeumann && (Nx == xIndex || Ny == yIndex)))
                                    {
                                        float heightAtPoint = heightMap[Tx + Nx + xShift, Ty + Ny + yShift]; // Get height at point
                                                                                // Tidal...
                                        hCumulative += heightAtPoint;
                                        // Others...
                                        t = heightAtIndex - heightAtPoint;
                                        if (t > 0)
                                        {
                                            tCumulative += t;
                                            if (t < tMin)
                                            {
                                                tMin = t;
                                            }
                                            if (t > tMax)
                                            {
                                                tMax = t;
                                            }
                                        }
                                        nNeighbours++;
                                    }
                                }
                            }
                        }
                        float tAverage = tCumulative / nNeighbours;
                        // float tAverage = tMax;
                        // Erosion type...
                        bool doErode = false;
                        switch (erosionType)
                        {
                            case ErosionType.Thermal:
                                if (tAverage >= minSlopeBlendMin)
                                {
                                    doErode = true;
                                }
                                break;
                            case ErosionType.Hydraulic:
                                if (tAverage > 0 && tAverage <= maxSlopeBlendMax)
                                {
                                    doErode = true;
                                }
                                break;
                            case ErosionType.Tidal:
                                if (tAverage > 0 && tAverage <= cliffLimit && heightAtIndex < upperTidalLimit && heightAtIndex > lowerTidalLimit)
                                {
                                    doErode = true;
                                }
                                break;
                            default:
                                return heightMap;
                        }
                        if (doErode)
                        {
                            float blendAmount;
                            if (erosionType == ErosionType.Tidal)
                            {
                                // Tidal...
                                float hAverage = hCumulative / (nNeighbours + 1);
                                float dTidalSeaLevel = Mathf.Abs(seaLevel - heightAtIndex);
                                blendAmount = dTidalSeaLevel / tidalRange;
                                float blendHeight = heightAtIndex * blendAmount + hAverage * (1 - blendAmount);
                                float blendTidalSeaLevel = Mathf.Pow(dTidalSeaLevel, 3);
                                heightMap[Tx + xIndex + xShift, Ty + yIndex + yShift] = seaLevel * blendTidalSeaLevel + blendHeight * (1 - blendTidalSeaLevel);
                            } else
                            {
                                // Thermal or Hydraulic...
                                float blendRange;
                                if (erosionType == ErosionType.Thermal)
                                {
                                    if (tAverage > minSlopeBlendMax)
                                    {
                                        blendAmount = 1;
                                    } else
                                    {
                                        blendRange = minSlopeBlendMax - minSlopeBlendMin;
                                        blendAmount = (tAverage - minSlopeBlendMin) / blendRange; // minSlopeBlendMin = 0; minSlopeBlendMax = 1
                                    }
                                } else
                                {
                                    if (tAverage < maxSlopeBlendMin)
                                    {
                                        blendAmount = 1;
                                    } else
                                    {
                                        blendRange = maxSlopeBlendMax - maxSlopeBlendMin;
                                        blendAmount = 1 - ((tAverage - maxSlopeBlendMin) / blendRange); // maxSlopeBlendMin = 1; maxSlopeBlendMax = 0
                                    }
                                }
                                float m = tMin / 2 * blendAmount;
                                float pointValue = heightMap[Tx + xIndex + xShift, Ty + yIndex + yShift];
                                if (erosionMode == ErosionMode.Filter || (erosionMode == ErosionMode.Brush && useDifferenceMaps))
                                {
                                    // Pass to difference map...
                                    float heightDiffAtIndexSoFar = heightDiffMap[Tx + xIndex + xShift, Ty + yIndex + yShift];
                                    float heightAtIndexDiff = heightDiffAtIndexSoFar - m;
                                    heightDiffMap[Tx + xIndex + xShift, Ty + yIndex + yShift] = heightAtIndexDiff;
                                } else
                                {
                                    float pointValueAfter = pointValue - m;
                                    if (pointValueAfter < 0)
                                    {
                                        pointValueAfter = 0;
                                    }
                                    heightMap[Tx + xIndex + xShift, Ty + yIndex + yShift] = pointValueAfter;
                                }
                                for (Ny = 0; Ny < yNeighbours; Ny++)
                                {
                                    for (Nx = 0; Nx < xNeighbours; Nx++)
                                    {
                                        if (Nx != xIndex || Ny != yIndex)
                                        {
                                            if (neighbourhood == Neighbourhood.Moore || (neighbourhood == Neighbourhood.VonNeumann && (Nx == xIndex || Ny == yIndex)))
                                            {
                                                float neighbourValue = heightMap[Tx + Nx + xShift, Ty + Ny + yShift];
                                                t = pointValue - neighbourValue;
                                                // Only move material downhill...
                                                if (t > 0)
                                                {
                                                    float mProportional = m * (t / tCumulative);
                                                    if (erosionMode == ErosionMode.Filter || (erosionMode == ErosionMode.Brush && useDifferenceMaps))
                                                    {
                                                        // Pass to difference map...
                                                        float heightDiffAtNeighbourSoFar = heightDiffMap[Tx + Nx + xShift, Ty + Ny + yShift];
                                                        float heightAtNeighbourDiff = heightDiffAtNeighbourSoFar + mProportional;
                                                        heightDiffMap[Tx + Nx + xShift, Ty + Ny + yShift] = heightAtNeighbourDiff;
                                                    } else
                                                    {
                                                        neighbourValue += mProportional;
                                                        if (neighbourValue < 0)
                                                        {
                                                            neighbourValue = 0;
                                                        }
                                                        heightMap[Tx + Nx + xShift, Ty + Ny + yShift] = neighbourValue;
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                if ((erosionMode == ErosionMode.Filter || (erosionMode == ErosionMode.Brush && useDifferenceMaps)) && erosionType != ErosionType.Tidal)
                {
                    // Apply height difference map to height map...
                    float heightAtCell;
                    for (Ty = 0; Ty < Th; Ty++)
                    {
                        for (Tx = 0; Tx < Tw; Tx++)
                        {
                            heightAtCell = heightMap[Tx, Ty] + heightDiffMap[Tx, Ty];
                            if (heightAtCell > 1.0f)
                            {
                                heightAtCell = 1.0f;
                            } else if (heightAtCell < 0.0f)
                            {
                                heightAtCell = 0.0f;
                            }
                            heightMap[Tx, Ty] = heightAtCell;
                            heightDiffMap[Tx, Ty] = 0.0f;
                        }
                    }
                }
                if (erosionMode == ErosionMode.Filter)
                {
                    // Update progress...
                    string titleString = "";
                    string displayString = "";
                    switch (erosionType)
                    {
                        case ErosionType.Thermal:
                            titleString = "Applying Thermal Erosion";
                            displayString = "Applying thermal erosion.";
                            break;
                        case ErosionType.Hydraulic:
                            titleString = "Applying Hydraulic Erosion";
                            displayString = "Applying hydraulic erosion.";
                            break;
                        case ErosionType.Tidal:
                            titleString = "Applying Tidal Erosion";
                            displayString = "Applying tidal erosion.";
                            break;
                        default:
                            return heightMap;
                    }
                    float percentComplete = (float)iter / (float)iterations;
                    erosionProgressDelegate(titleString, displayString, iter, iterations, percentComplete);
                }
            }
            return heightMap;
        }

        // -------------------------------------------------------------------------------------------------------- HYDRAULIC (VELOCITY) EROSION

        private float[,] velocityHydraulicErosion(float[,] heightMap, Vector2 arraySize, int iterations, ErosionProgressDelegate erosionProgressDelegate)
        {
            int Tx = (int)arraySize.x;
            int Ty = (int)arraySize.y;
            float[,] precipitationMap = new float[Tx, Ty];
            float[,] slopeMap = new float[Tx, Ty];
            float[,] waterMap = new float[Tx, Ty];
            float[,] waterDiffMap = new float[Tx, Ty];
            float[,] velocityMap = new float[Tx, Ty];
            float[,] velocityDiffMap = new float[Tx, Ty];
            float[,] sedimentMap = new float[Tx, Ty];
            float[,] sedimentDiffMap = new float[Tx, Ty];
            int Mx;
            int My;
            int xNeighbours;
            int yNeighbours;
            int xShift;
            int yShift;
            int xIndex;
            int yIndex;
            float maxSediment;
            // Zero maps...
            for (My = 0; My < Ty; My++)
            {
                for (Mx = 0; Mx < Tx; Mx++)
                {
                    waterMap[Mx, My] = 0.0f;
                    waterDiffMap[Mx, My] = 0.0f;
                    velocityMap[Mx, My] = 0.0f;
                    velocityDiffMap[Mx, My] = 0.0f;
                    sedimentMap[Mx, My] = 0.0f;
                    sedimentDiffMap[Mx, My] = 0.0f;
                }
            }
            // Cache precipitation map...
            for (My = 0; My < Ty; My++)
            {
                for (Mx = 0; Mx < Tx; Mx++)
                {
                    float precipitationAmount = heightMap[Mx, My];
                    precipitationMap[Mx, My] = precipitationAmount;
                }
            }
            // Cache slope map and initialise velocity map...
            float tCumulative;
            int Nx;
            int Ny;
            float t;
            float heightAtIndex;
            float heightAtPoint;
            int nNeighbours;
            for (My = 0; My < Ty; My++)
            {
                // y...
                if (My == 0)
                {
                    yNeighbours = 2;
                    yShift = 0;
                    yIndex = 0;
                } else if (My == Ty - 1)
                {
                    yNeighbours = 2;
                    yShift = -1;
                    yIndex = 1;
                } else
                {
                    yNeighbours = 3;
                    yShift = -1;
                    yIndex = 1;
                }
                for (Mx = 0; Mx < Tx; Mx++)
                {
                    // x...
                    if (Mx == 0)
                    {
                        xNeighbours = 2;
                        xShift = 0;
                        xIndex = 0;
                    } else if (Mx == Tx - 1)
                    {
                        xNeighbours = 2;
                        xShift = -1;
                        xIndex = 1;
                    } else
                    {
                        xNeighbours = 3;
                        xShift = -1;
                        xIndex = 1;
                    }
                    // Calculate slope and create velocity map...
                    tCumulative = 0.0f;
                    heightAtIndex = heightMap[Mx + xIndex + xShift, My + yIndex + yShift]; // Get height at index
                    nNeighbours = 0;
                    for (Ny = 0; Ny < yNeighbours; Ny++)
                    {
                        for (Nx = 0; Nx < xNeighbours; Nx++)
                        {
                            if (Nx != xIndex || Ny != yIndex)
                            {
                                if (neighbourhood == Neighbourhood.Moore || (neighbourhood == Neighbourhood.VonNeumann && (Nx == xIndex || Ny == yIndex)))
                                {
                                    heightAtPoint = heightMap[Mx + Nx + xShift, My + Ny + yShift]; // Get height at point
                                    t = Mathf.Abs(heightAtIndex - heightAtPoint);
                                    tCumulative += t;
                                    nNeighbours++;
                                }
                            }
                        }
                    }
                    float tAverage = tCumulative / nNeighbours;
                    slopeMap[Mx + xIndex + xShift, My + yIndex + yShift] = tAverage;
                    // velocityMap[Mx + xIndex + xShift, My + yIndex + yShift] = Mathf.Min(tAverage * hydraulicVelocity, 1.0f);
                }
            }
            // Start iterations...
            for (int iter = 0; iter < iterations; iter++)
            {
                // Add water proportional to precipitation...
                for (My = 0; My < Ty; My++)
                {
                    for (Mx = 0; Mx < Tx; Mx++)
                    {
                        float waterAmount = waterMap[Mx, My] + precipitationMap[Mx, My] * hydraulicVelocityRainfall;
                        if (waterAmount > 1.0f)
                        {
                            waterAmount = 1.0f;
                        }
                        waterMap[Mx, My] = waterAmount;
                    }
                }
                // Dissolve material as sediment...
                for (My = 0; My < Ty; My++)
                {
                    for (Mx = 0; Mx < Tx; Mx++)
                    {
                        float sedimentAmount = sedimentMap[Mx, My];
                        maxSediment = waterMap[Mx, My] * hydraulicVelocitySedimentSaturation;
                        if (sedimentAmount < maxSediment)
                        {
                            float erodedSediment = waterMap[Mx, My] * velocityMap[Mx, My] * hydraulicVelocitySedimentSolubility;
                            if (sedimentAmount + erodedSediment > maxSediment)
                            {
                                erodedSediment = maxSediment - sedimentAmount;
                            }
                            heightAtIndex = heightMap[Mx, My];
                            if (erodedSediment > heightAtIndex)
                            {
                                erodedSediment = heightAtIndex;
                            }
                            sedimentMap[Mx, My] = sedimentAmount + erodedSediment;
                            heightMap[Mx, My] = heightAtIndex - erodedSediment;
                        }
                    }
                }
                // Calculate velocity and move water...
                for (My = 0; My < Ty; My++)
                {
                    // y...
                    if (My == 0)
                    {
                        yNeighbours = 2;
                        yShift = 0;
                        yIndex = 0;
                    } else if (My == Ty - 1)
                    {
                        yNeighbours = 2;
                        yShift = -1;
                        yIndex = 1;
                    } else
                    {
                        yNeighbours = 3;
                        yShift = -1;
                        yIndex = 1;
                    }
                    for (Mx = 0; Mx < Tx; Mx++)
                    {
                        // x...
                        if (Mx == 0)
                        {
                            xNeighbours = 2;
                            xShift = 0;
                            xIndex = 0;
                        } else if (Mx == Tx - 1)
                        {
                            xNeighbours = 2;
                            xShift = -1;
                            xIndex = 1;
                        } else
                        {
                            xNeighbours = 3;
                            xShift = -1;
                            xIndex = 1;
                        }
                        // Calculate slope...
                        tCumulative = 0.0f;
                        heightAtIndex = heightMap[Mx, My]; // Get height at index
                        float hCumulative = heightAtIndex;
                        float waterAtIndex = waterMap[Mx, My]; // Get water at index
                        nNeighbours = 0;
                        float waterAtPoint;
                        for (Ny = 0; Ny < yNeighbours; Ny++)
                        {
                            for (Nx = 0; Nx < xNeighbours; Nx++)
                            {
                                if (Nx != xIndex || Ny != yIndex)
                                {
                                    if (neighbourhood == Neighbourhood.Moore || (neighbourhood == Neighbourhood.VonNeumann && (Nx == xIndex || Ny == yIndex)))
                                    {
                                        heightAtPoint = heightMap[Mx + Nx + xShift, My + Ny + yShift]; // Get height at point
                                        waterAtPoint = waterMap[Mx + Nx + xShift, My + Ny + yShift]; // Get water at point
                                        t = (heightAtIndex + waterAtIndex) - (heightAtPoint + waterAtPoint);
                                        // Only calculate downhill cells...
                                        if (t > 0)
                                        {
                                            tCumulative += t;
                                            hCumulative += heightAtIndex + waterAtIndex;
                                            nNeighbours++;
                                        }
                                    }
                                }
                            }
                        }
                        float velocityAtIndex = velocityMap[Mx, My];
                        float slopeAtIndex = slopeMap[Mx, My];
                        float sedimentAtIndex = sedimentMap[Mx, My];
                        float totalVelocityAtIndex = velocityAtIndex + (hydraulicVelocity * slopeAtIndex);
                        // Calculate water to be transported away from the index...
                        float hAverage = hCumulative / (nNeighbours + 1);
                        float dWater = (heightAtIndex + waterAtIndex) - hAverage;
                        float transportedWater = Mathf.Min(waterAtIndex, dWater * (1.0f + velocityAtIndex));
                        float waterDiffAtIndexSoFar = waterDiffMap[Mx, My];
                        // Pass to difference map...
                        float waterAtIndexDiff = waterDiffAtIndexSoFar - transportedWater;
                        waterDiffMap[Mx, My] = waterAtIndexDiff;
                        float transferredVelocity = totalVelocityAtIndex * (transportedWater / waterAtIndex);
                        float transferredSediment = sedimentAtIndex * (transportedWater / waterAtIndex);
                        // Neighbours...
                        for (Ny = 0; Ny < yNeighbours; Ny++)
                        {
                            for (Nx = 0; Nx < xNeighbours; Nx++)
                            {
                                if (Nx != xIndex || Ny != yIndex)
                                {
                                    if (neighbourhood == Neighbourhood.Moore || (neighbourhood == Neighbourhood.VonNeumann && (Nx == xIndex || Ny == yIndex)))
                                    {
                                        heightAtPoint = heightMap[Mx + Nx + xShift, My + Ny + yShift]; // Get height at point
                                        waterAtPoint = waterMap[Mx + Nx + xShift, My + Ny + yShift]; // Get water at point
                                        t = (heightAtIndex + waterAtIndex) - (heightAtPoint + waterAtPoint);
                                        // Only affect downhill cells...
                                        if (t > 0.0f)
                                        {
                                            // Move water...
                                            float transportedWaterSoFar = waterDiffMap[Mx + Nx + xShift, My + Ny + yShift];
                                            float transportedWaterProportional = transportedWater * (t / tCumulative);
                                            float transportedWaterAfter = transportedWaterSoFar + transportedWaterProportional;
                                            // Pass to difference map...
                                            waterDiffMap[Mx + Nx + xShift, My + Ny + yShift] = transportedWaterAfter;
                                            // Transfer velocity...
                                            float transferredVelocitySoFar = velocityDiffMap[Mx + Nx + xShift, My + Ny + yShift];
                                            float transferredVelocityProportional = transferredVelocity * hydraulicMomentum * (t / tCumulative);
                                            float transferredVelocityAfter = transferredVelocitySoFar + transferredVelocityProportional;
                                            // Pass to difference map...
                                            velocityDiffMap[Mx + Nx + xShift, My + Ny + yShift] = transferredVelocityAfter;
                                            // Transfer sediment...
                                            float transferredSedimentSoFar = sedimentDiffMap[Mx + Nx + xShift, My + Ny + yShift];
                                            float transferredSedimentProportional = transferredSediment * hydraulicMomentum * (t / tCumulative);
                                            float transferredSedimentAfter = transferredSedimentSoFar + transferredSedimentProportional;
                                            // Pass to difference map...
                                            sedimentDiffMap[Mx + Nx + xShift, My + Ny + yShift] = transferredSedimentAfter;
                                        }
                                    }
                                }
                            }
                        }
                        // Lose velocity at index...
                        float velocityDiffSoFar = velocityDiffMap[Mx, My];
                        velocityDiffMap[Mx, My] = velocityDiffSoFar - transferredVelocity;
                    }
                }
                // Apply velocity difference map to velocity map...
                float velocityAtCell;
                for (My = 0; My < Ty; My++)
                {
                    for (Mx = 0; Mx < Tx; Mx++)
                    {
                        velocityAtCell = velocityMap[Mx, My] + velocityDiffMap[Mx, My];
                        // Calculate entropy...
                        velocityAtCell *= 1.0f - hydraulicEntropy;
                        if (velocityAtCell > 1.0f)
                        {
                            velocityAtCell = 1.0f;
                        } else if (velocityAtCell < 0.0f)
                        {
                            velocityAtCell = 0.0f;
                        }
                        velocityMap[Mx, My] = velocityAtCell;
                        velocityDiffMap[Mx, My] = 0.0f;
                    }
                }
                // Apply water difference map to water map...
                float waterAtCell;
                for (My = 0; My < Ty; My++)
                {
                    for (Mx = 0; Mx < Tx; Mx++)
                    {
                        waterAtCell = waterMap[Mx, My] + waterDiffMap[Mx, My];
                        // Calculate evaporation...
                        float evaporatedWater = waterAtCell * hydraulicVelocityEvaporation;
                        waterAtCell -= evaporatedWater;
                        if (waterAtCell > 1.0f)
                        {
                            waterAtCell = 1.0f;
                        } else if (waterAtCell < 0.0f)
                        {
                            waterAtCell = 0.0f;
                        }
                        waterMap[Mx, My] = waterAtCell;
                        waterDiffMap[Mx, My] = 0.0f;
                    }
                }
                // Apply sediment difference map to sediment map...
                float sedimentAtCell;
                for (My = 0; My < Ty; My++)
                {
                    for (Mx = 0; Mx < Tx; Mx++)
                    {
                        sedimentAtCell = sedimentMap[Mx, My] + sedimentDiffMap[Mx, My];
                        if (sedimentAtCell > 1.0f)
                        {
                            sedimentAtCell = 1.0f;
                        } else if (sedimentAtCell < 0.0f)
                        {
                            sedimentAtCell = 0.0f;
                        }
                        sedimentMap[Mx, My] = sedimentAtCell;
                        sedimentDiffMap[Mx, My] = 0.0f;
                    }
                }
                // Deposit sediment...
                float heightAtCell;
                for (My = 0; My < Ty; My++)
                {
                    for (Mx = 0; Mx < Tx; Mx++)
                    {
                        maxSediment = waterMap[Mx, My] * hydraulicVelocitySedimentSaturation;
                        sedimentAtCell = sedimentMap[Mx, My];
                        if (sedimentAtCell > maxSediment)
                        {
                            float depositedSediment = sedimentAtCell - maxSediment;
                            sedimentMap[Mx, My] = maxSediment;
                            heightAtCell = heightMap[Mx, My];
                            heightMap[Mx, My] = heightAtCell + depositedSediment;
                        }
                    }
                }
                // Downcutting...
                for (My = 0; My < Ty; My++)
                {
                    for (Mx = 0; Mx < Tx; Mx++)
                    {
                        velocityAtCell = waterMap[Mx, My];
                        heightAtCell = heightMap[Mx, My];
                        float heightModifier = 1 - (Mathf.Abs(0.5f - heightAtCell) * 2);
                        float cutMaterial = hydraulicDowncutting * velocityAtCell * heightModifier;
                        heightAtCell -= cutMaterial;
                        heightMap[Mx, My] = heightAtCell;
                    }
                }
                // Update progress...
                float percentComplete = (float)iter / (float)iterations;
                erosionProgressDelegate("Applying Hydraulic Erosion", "Applying hydraulic erosion.", iter, iterations, percentComplete);
            }
            return heightMap;
        }

        // -------------------------------------------------------------------------------------------------------- HYDRAULIC (FULL) EROSION

        private float[,] fullHydraulicErosion(float[,] heightMap, Vector2 arraySize, int iterations, ErosionProgressDelegate erosionProgressDelegate)
        {
            int Tx = (int)arraySize.x;
            int Ty = (int)arraySize.y;
            float[,] waterMap = new float[Tx, Ty];
            float[,] waterDiffMap = new float[Tx, Ty];
            float[,] sedimentMap = new float[Tx, Ty];
            float[,] sedimentDiffMap = new float[Tx, Ty];
            int Mx;
            int My;
            int xNeighbours;
            int yNeighbours;
            int xShift;
            int yShift;
            int xIndex;
            int yIndex;
            int Nx;
            int Ny;
            float t;
            float heightAtIndex;
            float heightAtPoint;
            int nNeighbours;
            float maxSediment;
            float sedimentAtCell;
            float heightAtCell;
            // Zero maps...
            for (My = 0; My < Ty; My++)
            {
                for (Mx = 0; Mx < Tx; Mx++)
                {
                    waterMap[Mx, My] = 0.0f;
                    waterDiffMap[Mx, My] = 0.0f;
                    sedimentMap[Mx, My] = 0.0f;
                    sedimentDiffMap[Mx, My] = 0.0f;
                }
            }
            // Start iterations...
            for (int iter = 0; iter < iterations; iter++)
            {
                // Add water proportional to precipitation...
                for (My = 0; My < Ty; My++)
                {
                    for (Mx = 0; Mx < Tx; Mx++)
                    {
                        float waterAmount = waterMap[Mx, My] + hydraulicRainfall;
                        if (waterAmount > 1.0f)
                        {
                            waterAmount = 1.0f;
                        }
                        waterMap[Mx, My] = waterAmount;
                    }
                }
                // Dissolve material as sediment...
                for (My = 0; My < Ty; My++)
                {
                    for (Mx = 0; Mx < Tx; Mx++)
                    {
                        float sedimentAmount = sedimentMap[Mx, My];
                        maxSediment = waterMap[Mx, My] * hydraulicSedimentSaturation;
                        if (sedimentAmount < maxSediment)
                        {
                            float erodedSediment = waterMap[Mx, My] * hydraulicSedimentSolubility;
                            if (sedimentAmount + erodedSediment > maxSediment)
                            {
                                erodedSediment = maxSediment - sedimentAmount;
                            }
                            heightAtIndex = heightMap[Mx, My];
                            if (erodedSediment > heightAtIndex)
                            {
                                erodedSediment = heightAtIndex;
                            }
                            sedimentMap[Mx, My] = sedimentAmount + erodedSediment;
                            heightMap[Mx, My] = heightAtIndex - erodedSediment;
                        }
                    }
                }
                // Find slope and move water...
                for (My = 0; My < Ty; My++)
                {
                    // y...
                    if (My == 0)
                    {
                        yNeighbours = 2;
                        yShift = 0;
                        yIndex = 0;
                    } else if (My == Ty - 1)
                    {
                        yNeighbours = 2;
                        yShift = -1;
                        yIndex = 1;
                    } else
                    {
                        yNeighbours = 3;
                        yShift = -1;
                        yIndex = 1;
                    }
                    for (Mx = 0; Mx < Tx; Mx++)
                    {
                        // x...
                        if (Mx == 0)
                        {
                            xNeighbours = 2;
                            xShift = 0;
                            xIndex = 0;
                        } else if (Mx == Tx - 1)
                        {
                            xNeighbours = 2;
                            xShift = -1;
                            xIndex = 1;
                        } else
                        {
                            xNeighbours = 3;
                            xShift = -1;
                            xIndex = 1;
                        }
                        // Calculate slope...
                        float tCumulative = 0.0f;
                        float tMax = 0.0f;
                        heightAtIndex = heightMap[Mx + xIndex + xShift, My + yIndex + yShift]; // Get height at index
                        float waterAtIndex = waterMap[Mx + xIndex + xShift, My + yIndex + yShift]; // Get water at index
                        float waterAtPoint;
                        float hCumulative = heightAtIndex;
                        nNeighbours = 0;
                        for (Ny = 0; Ny < yNeighbours; Ny++)
                        {
                            for (Nx = 0; Nx < xNeighbours; Nx++)
                            {
                                if (Nx != xIndex || Ny != yIndex)
                                {
                                    if (neighbourhood == Neighbourhood.Moore || (neighbourhood == Neighbourhood.VonNeumann && (Nx == xIndex || Ny == yIndex)))
                                    {
                                        heightAtPoint = heightMap[Mx + Nx + xShift, My + Ny + yShift]; // Get height at point
                                        waterAtPoint = waterMap[Mx + Nx + xShift, My + Ny + yShift]; // Get water at point
                                        t = (heightAtIndex + waterAtIndex) - (heightAtPoint + waterAtPoint);
                                        if (t > 0)
                                        {
                                            tCumulative += t;
                                            hCumulative += heightAtPoint + waterAtPoint;
                                            nNeighbours++;
                                            if (t > tMax)
                                            {
                                                t = tMax;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        // Calculate water to be transported away from the index...
                        float hAverage = hCumulative / (nNeighbours + 1);
                        float dWater = (heightAtIndex + waterAtIndex) - hAverage;
                        float transportedWater = Mathf.Min(waterAtIndex, dWater);
                        float waterDiffAtIndexSoFar = waterDiffMap[Mx + xIndex + xShift, My + yIndex + yShift];
                        float waterAtIndexDiff = waterDiffAtIndexSoFar - transportedWater;
                        // Pass to difference map...
                        waterDiffMap[Mx + xIndex + xShift, My + yIndex + yShift] = waterAtIndexDiff;
                        // Calculate sediment to be transported away from the index...
                        float sedimentAtIndex = sedimentMap[Mx + xIndex + xShift, My + yIndex + yShift];
                        float transportedSediment = sedimentAtIndex * (transportedWater / waterAtIndex);
                        float sedimentDiffAtIndexSoFar = sedimentDiffMap[Mx + xIndex + xShift, My + yIndex + yShift];
                        float sedimentAtIndexDiff = sedimentDiffAtIndexSoFar - transportedSediment;
                        // Pass to difference map...
                        sedimentDiffMap[Mx + xIndex + xShift, My + yIndex + yShift] = sedimentAtIndexDiff;
                        // Neighbours...
                        for (Ny = 0; Ny < yNeighbours; Ny++)
                        {
                            for (Nx = 0; Nx < xNeighbours; Nx++)
                            {
                                if (Nx != xIndex || Ny != yIndex)
                                {
                                    if (neighbourhood == Neighbourhood.Moore || (neighbourhood == Neighbourhood.VonNeumann && (Nx == xIndex || Ny == yIndex)))
                                    {
                                        heightAtPoint = heightMap[Mx + Nx + xShift, My + Ny + yShift]; // Get height at point
                                        waterAtPoint = waterMap[Mx + Nx + xShift, My + Ny + yShift]; // Get water at point
                                        t = (heightAtIndex + waterAtIndex) - (heightAtPoint + waterAtPoint);
                                        // Only affect downhill cells...
                                        if (t > 0)
                                        {
                                            // Move water...
                                            float transportedWaterSoFar = waterDiffMap[Mx + Nx + xShift, My + Ny + yShift];
                                            float transportedWaterProportional = transportedWater * (t / tCumulative);
                                            float transportedWaterAfter = transportedWaterSoFar + transportedWaterProportional;
                                            // Pass to difference map...
                                            waterDiffMap[Mx + Nx + xShift, My + Ny + yShift] = transportedWaterAfter;
                                            // Move sediment...
                                            float transportedSedimentSoFar = sedimentDiffMap[Mx + Nx + xShift, My + Ny + yShift];
                                            float transportedSedimentProportional = transportedSediment * (t / tCumulative);
                                            float transportedSedimentAfter = transportedSedimentSoFar + transportedSedimentProportional;
                                            // Pass to difference map...
                                            sedimentDiffMap[Mx + Nx + xShift, My + Ny + yShift] = transportedSedimentAfter;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                // Apply water difference map to water map...
                float waterAtCell;
                for (My = 0; My < Ty; My++)
                {
                    for (Mx = 0; Mx < Tx; Mx++)
                    {
                        waterAtCell = waterMap[Mx, My] + waterDiffMap[Mx, My];
                        // Calculate evaporation...
                        float evaporatedWater = waterAtCell * hydraulicEvaporation;
                        waterAtCell -= evaporatedWater;
                        if (waterAtCell < 0.0f)
                        {
                            waterAtCell = 0.0f;
                        }
                        waterMap[Mx, My] = waterAtCell;
                        waterDiffMap[Mx, My] = 0.0f;
                    }
                }
                // Apply sediment difference map to sediment map...
                for (My = 0; My < Ty; My++)
                {
                    for (Mx = 0; Mx < Tx; Mx++)
                    {
                        sedimentAtCell = sedimentMap[Mx, My] + sedimentDiffMap[Mx, My];
                        if (sedimentAtCell > 1.0f)
                        {
                            sedimentAtCell = 1.0f;
                        } else if (sedimentAtCell < 0.0f)
                        {
                            sedimentAtCell = 0.0f;
                        }
                        sedimentMap[Mx, My] = sedimentAtCell;
                        sedimentDiffMap[Mx, My] = 0.0f;
                    }
                }
                // Deposit sediment...
                for (My = 0; My < Ty; My++)
                {
                    for (Mx = 0; Mx < Tx; Mx++)
                    {
                        maxSediment = waterMap[Mx, My] * hydraulicSedimentSaturation;
                        sedimentAtCell = sedimentMap[Mx, My];
                        if (sedimentAtCell > maxSediment)
                        {
                            float depositedSediment = sedimentAtCell - maxSediment;
                            sedimentMap[Mx, My] = maxSediment;
                            heightAtCell = heightMap[Mx, My];
                            heightMap[Mx, My] = heightAtCell + depositedSediment;
                        }
                    }
                }
                // Update progress...
                float percentComplete = (float)iter / (float)iterations;
                erosionProgressDelegate("Applying Hydraulic Erosion", "Applying hydraulic erosion.", iter, iterations, percentComplete);
            }
            return heightMap;
        }

        // -------------------------------------------------------------------------------------------------------- WIND EROSION

        private float[,] windErosion(float[,] heightMap, Vector2 arraySize, int iterations, ErosionProgressDelegate erosionProgressDelegate)
        {
            Terrain ter = (Terrain)GetComponent(typeof(Terrain));
            TerrainData terData = ter.terrainData;
            Quaternion windQuaternion = Quaternion.Euler(0, (windDirection + 180.0f), 0);
            Vector3 windVector = windQuaternion * Vector3.forward;
            int Tw = (int)arraySize.x;
            int Th = (int)arraySize.y;
            float[,] stressMap = new float[Tw, Th];
            float[,] flowMap = new float[Tw, Th];
            float[,] velocityMap = new float[Tw, Th];
            float[,] velocityDiffMap = new float[Tw, Th];
            float[,] suspensionMap = new float[Tw, Th];
            float[,] suspensionDiffMap = new float[Tw, Th];
            float[,] heightDiffMap = new float[Tw, Th];
            int xNeighbours;
            int yNeighbours;
            int xShift;
            int yShift;
            int xIndex;
            int yIndex;
            int Mx;
            int My;
            float materialAtIndex;
            float velocityAtCell;
            // Zero maps...
            for (My = 0; My < Th; My++)
            {
                for (Mx = 0; Mx < Tw; Mx++)
                {
                    stressMap[Mx, My] = 0.0f;
                    flowMap[Mx, My] = 0.0f;
                    velocityMap[Mx, My] = 0.0f;
                    velocityDiffMap[Mx, My] = 0.0f;
                    suspensionMap[Mx, My] = 0.0f;
                    suspensionDiffMap[Mx, My] = 0.0f;
                    heightDiffMap[Mx, My] = 0.0f;
                }
            }
            // Start iterations...
            for (int iter = 0; iter < iterations; iter++)
            {
                // Drop material...
                for (My = 0; My < Th; My++)
                {
                    for (Mx = 0; Mx < Tw; Mx++)
                    {
                        velocityAtCell = velocityMap[Mx, My];
                        float heightAtCell = heightMap[Mx, My];
                        float materialAtCell = suspensionMap[Mx, My];
                        float droppedMaterial = materialAtCell * windGravity; //  * 1.0f - (velocityAtCell)
                        suspensionMap[Mx, My] = materialAtCell - droppedMaterial;
                        heightMap[Mx, My] = heightAtCell + droppedMaterial;
                    }
                }
                // Calculate stress and flow...
                for (My = 0; My < Th; My++)
                {
                    for (Mx = 0; Mx < Tw; Mx++)
                    {
                        float heightAtIndex = heightMap[Mx, My]; // ALTITUDE
                        Vector3 pNormal = terData.GetInterpolatedNormal((float)Mx / Tw, (float)My / Th); // NORMAL
                        float stress = (Vector3.Angle(pNormal, windVector) - 90) / 90;
                        if (stress < 0.0f)
                        {
                            stress = 0.0f;
                        }
                        stressMap[Mx, My] = stress * heightAtIndex;
                        float flow = 1.0f - Mathf.Abs(Vector3.Angle(pNormal, windVector) - 90) / 90;
                        flowMap[Mx, My] = flow * heightAtIndex; // ^2
                        float inFlow = flow * heightAtIndex * windForce; // ^2
                        float velocityAtPoint = velocityMap[Mx, My];
                        float newVelocityAtPoint = velocityAtPoint + inFlow; // * UnityEngine.Random.value;
                        velocityMap[Mx, My] = newVelocityAtPoint;
                        // Lift material...
                        materialAtIndex = suspensionMap[Mx, My];
                        float liftedMaterial = windLift * newVelocityAtPoint; // * UnityEngine.Random.value;
                        if (materialAtIndex + liftedMaterial > windCapacity)
                        {
                            liftedMaterial = windCapacity - materialAtIndex;
                        }
                        suspensionMap[Mx, My] = materialAtIndex + liftedMaterial;
                        heightMap[Mx, My] = heightAtIndex - liftedMaterial;
                    }
                }
                // Calculate flow propagation...
                for (My = 0; My < Th; My++)
                {
                    // y...
                    if (My == 0)
                    {
                        yNeighbours = 2;
                        yShift = 0;
                        yIndex = 0;
                    } else if (My == Th - 1)
                    {
                        yNeighbours = 2;
                        yShift = -1;
                        yIndex = 1;
                    } else
                    {
                        yNeighbours = 3;
                        yShift = -1;
                        yIndex = 1;
                    }
                    for (Mx = 0; Mx < Tw; Mx++)
                    {
                        // x...
                        if (Mx == 0)
                        {
                            xNeighbours = 2;
                            xShift = 0;
                            xIndex = 0;
                        } else if (Mx == Tw - 1)
                        {
                            xNeighbours = 2;
                            xShift = -1;
                            xIndex = 1;
                        } else
                        {
                            xNeighbours = 3;
                            xShift = -1;
                            xIndex = 1;
                        }
                        int Ny;
                        int Nx;
                        float flowAtIndex = flowMap[Mx, My];
                        float stressAtIndex = stressMap[Mx, My];
                        materialAtIndex = suspensionMap[Mx, My];
                        for (Ny = 0; Ny < yNeighbours; Ny++)
                        {
                            for (Nx = 0; Nx < xNeighbours; Nx++)
                            {
                                if (Nx != xIndex || Ny != yIndex)
                                {
                                    if (neighbourhood == Neighbourhood.Moore || (neighbourhood == Neighbourhood.VonNeumann && (Nx == xIndex || Ny == yIndex)))
                                    {
                                        Vector3 vectorToNeighbour = new Vector3(Nx + xShift, 0, -1 * (Ny + yShift));
                                        float pWeighting = (90 - Vector3.Angle(vectorToNeighbour, windVector)) / 90;
                                        if (pWeighting < 0.0f)
                                        {
                                            pWeighting = 0.0f;
                                        }
                                        // Propagate velocity...
                                        float propagatedVelocitySoFar = velocityDiffMap[Mx + Nx + xShift, My + Ny + yShift];
                                        float propagatedVelocity = pWeighting * (flowAtIndex - stressAtIndex) * 0.1f;
                                        if (propagatedVelocity < 0.0f)
                                        {
                                            propagatedVelocity = 0.0f;
                                        }
                                        float propagatedVelocityAfter = propagatedVelocitySoFar + propagatedVelocity;
                                        // Pass to difference map...
                                        velocityDiffMap[Mx + Nx + xShift, My + Ny + yShift] = propagatedVelocityAfter;
                                        // Lose velocity...
                                        float lostVelocitySoFar = velocityDiffMap[Mx, My];
                                        float lostVelocityAfter = lostVelocitySoFar - propagatedVelocity;
                                        // Pass to difference map...
                                        velocityDiffMap[Mx, My] = lostVelocityAfter;
                                        // Propagate material...
                                        float propagatedMaterialSoFar = suspensionDiffMap[Mx + Nx + xShift, My + Ny + yShift];
                                        float propagatedMaterial = materialAtIndex * propagatedVelocity;
                                        float propagatedMaterialAfter = propagatedMaterialSoFar + propagatedMaterial;
                                        // Pass to difference map...
                                        suspensionDiffMap[Mx + Nx + xShift, My + Ny + yShift] = propagatedMaterialAfter;
                                        // Lose material...
                                        float lostMaterialSoFar = suspensionDiffMap[Mx, My];
                                        float lostMaterialAfter = lostMaterialSoFar - propagatedMaterial;
                                        // Pass to difference map...
                                        suspensionDiffMap[Mx, My] = lostMaterialAfter;
                                    }
                                }
                            }
                        }
                    }
                }
                // Apply suspension difference map to suspension map...
                float suspensionAtCell;
                for (My = 0; My < Th; My++)
                {
                    for (Mx = 0; Mx < Tw; Mx++)
                    {
                        suspensionAtCell = suspensionMap[Mx, My] + suspensionDiffMap[Mx, My];
                        if (suspensionAtCell > 1.0f)
                        {
                            suspensionAtCell = 1.0f;
                        } else if (suspensionAtCell < 0.0f)
                        {
                            suspensionAtCell = 0.0f;
                        }
                        suspensionMap[Mx, My] = suspensionAtCell;
                        suspensionDiffMap[Mx, My] = 0.0f;
                    }
                }
                // Apply velocity difference map to velocity map...
                for (My = 0; My < Th; My++)
                {
                    for (Mx = 0; Mx < Tw; Mx++)
                    {
                        velocityAtCell = velocityMap[Mx, My] + velocityDiffMap[Mx, My];
                        // Calculate entropy...
                        velocityAtCell *= 1.0f - windEntropy;
                        if (velocityAtCell > 1.0f)
                        {
                            velocityAtCell = 1.0f;
                        } else if (velocityAtCell < 0.0f)
                        {
                            velocityAtCell = 0.0f;
                        }
                        velocityMap[Mx, My] = velocityAtCell;
                        velocityDiffMap[Mx, My] = 0.0f;
                    }
                }
                // Smooth...
                smoothIterations = 1;
                smoothBlend = 0.25f;
                float[,] smoothedHeightMap = (float[,])heightMap.Clone();
                GeneratorProgressDelegate generatorProgressDelegate = new GeneratorProgressDelegate(dummyGeneratorProgress);
                smoothedHeightMap = smooth(smoothedHeightMap, arraySize, generatorProgressDelegate);
                // Combine...
                for (My = 0; My < Th; My++)
                {
                    for (Mx = 0; Mx < Tw; Mx++)
                    {
                        float oldHeightAtPoint = heightMap[Mx, My];
                        float newHeightAtPoint = smoothedHeightMap[Mx, My];
                        float blendAmount = stressMap[Mx, My] * windSmoothing;
                        float blendedHeightAtPoint = (newHeightAtPoint * blendAmount) + (oldHeightAtPoint * (1.0f - blendAmount));
                        heightMap[Mx, My] = blendedHeightAtPoint;
                    }
                }
                // Update progress...
                float percentComplete = (float)iter / (float)iterations;
                erosionProgressDelegate("Applying Wind Erosion", "Applying wind erosion.", iter, iterations, percentComplete);
            }
            return heightMap;
        }

        // -------------------------------------------------------------------------------------------------------- PROCEDURAL TERRAIN TEXTURING

        public void textureTerrain(TextureProgressDelegate textureProgressDelegate)
        {
            Terrain ter = (Terrain)GetComponent(typeof(Terrain));
            if (ter == null)
            {
                return;
            }
            TerrainData terData = ter.terrainData;
            splatPrototypes = terData.splatPrototypes;
            int nTextures = splatPrototypes.Length;
            if (nTextures < 2)
            {
                Debug.LogError("Error: You must assign at least 2 textures.");
                return;
            }
            textureProgressDelegate("Procedural Terrain Texture", "Generating height and slope maps. Please wait.", 0.1f);
            int Tw = terData.heightmapWidth - 1;
            int Th = terData.heightmapHeight - 1;
            float[,] heightMapData = new float[Tw, Th];
            float[,] slopeMapData = new float[Tw, Th];
            float[,,] splatMapData;
            terData.alphamapResolution = Tw;
            splatMapData = terData.GetAlphamaps(0, 0, Tw, Tw);
            // Angles to difference...
            Vector3 terSize = terData.size;
            float slopeBlendMinimum = ((terSize.x / Tw) * Mathf.Tan(slopeBlendMinAngle * Mathf.Deg2Rad)) / terSize.y;
            float slopeBlendMaximum = ((terSize.x / Tw) * Mathf.Tan(slopeBlendMaxAngle * Mathf.Deg2Rad)) / terSize.y;
            try
            {
                float greatestHeight = 0.0f;
                int xNeighbours;
                int yNeighbours;
                int xShift;
                int yShift;
                int xIndex;
                int yIndex;
                float[,] heightMap = terData.GetHeights(0, 0, Tw, Th);
                for (int Ty = 0; Ty < Th; Ty++)
                {
                    // y...
                    if (Ty == 0)
                    {
                        yNeighbours = 2;
                        yShift = 0;
                        yIndex = 0;
                    } else if (Ty == Th - 1)
                    {
                        yNeighbours = 2;
                        yShift = -1;
                        yIndex = 1;
                    } else
                    {
                        yNeighbours = 3;
                        yShift = -1;
                        yIndex = 1;
                    }
                    for (int Tx = 0; Tx < Tw; Tx++)
                    {
                        // x...
                        if (Tx == 0)
                        {
                            xNeighbours = 2;
                            xShift = 0;
                            xIndex = 0;
                        } else if (Tx == Tw - 1)
                        {
                            xNeighbours = 2;
                            xShift = -1;
                            xIndex = 1;
                        } else
                        {
                            xNeighbours = 3;
                            xShift = -1;
                            xIndex = 1;
                        }
                        // Get height...
                        float h = heightMap[Tx + xIndex + xShift, Ty + yIndex + yShift];
                        if (h > greatestHeight)
                        {
                            greatestHeight = h;
                        }
                        // ...and apply to height map...
                        heightMapData[Tx, Ty] = h;
                        // Calculate slope...
                        float tCumulative = 0.0f;
                        float nNeighbours = xNeighbours * yNeighbours - 1;
                        int Ny;
                        int Nx;
                        float t;
                        for (Ny = 0; Ny < yNeighbours; Ny++)
                        {
                            for (Nx = 0; Nx < xNeighbours; Nx++)
                            {
                                // Ignore the index...
                                if (Nx != xIndex || Ny != yIndex)
                                {
                                    t = Mathf.Abs(h - heightMap[Tx + Nx + xShift, Ty + Ny + yShift]);
                                    tCumulative += t;
                                }
                            }
                        }
                        float tAverage = tCumulative / nNeighbours;
                        // ...and apply to the slope map...
                        slopeMapData[Tx, Ty] = tAverage;
                    }
                    // Show progress...
                    // float completePoints = Ty * Th;
                    // float totalPoints = Tw * Th;
                    // float percentComplete = (completePoints / totalPoints) * 0.6f;
                    // textureProgressDelegate("Procedural Terrain Texture", "Generating height and slope maps. Please wait.", percentComplete);
                }
                // Blend slope...
                float sBlended;
                int Px;
                int Py;
                for (Py = 0; Py < Th; Py++)
                {
                    for (Px = 0; Px < Tw; Px++)
                    {
                        sBlended = slopeMapData[Px, Py];
                        if (sBlended < slopeBlendMinimum)
                        {
                            sBlended = 0;
                        } else if (sBlended < slopeBlendMaximum)
                        {
                            sBlended = (sBlended - slopeBlendMinimum) / (slopeBlendMaximum - slopeBlendMinimum);
                        } else if (sBlended > slopeBlendMaximum)
                        {
                            sBlended = 1;
                        }
                        slopeMapData[Px, Py] = sBlended;
                        splatMapData[Px, Py, 0] = sBlended;
                    }
                }
                // Blend height maps...
                for (int i = 1; i < nTextures; i++)
                {
                    for (Py = 0; Py < Th; Py++)
                    {
                        for (Px = 0; Px < Tw; Px++)
                        {
                            float hBlendInMinimum = 0;
                            float hBlendInMaximum = 0;
                            float hBlendOutMinimum = 1;
                            float hBlendOutMaximum = 1;
                            float hValue;
                            float hBlended = 0;
                            if (i > 1)
                            {
                                hBlendInMinimum = (float)heightBlendPoints[i * 2 - 4];
                                hBlendInMaximum = (float)heightBlendPoints[i * 2 - 3];
                            }
                            if (i < nTextures - 1)
                            {
                                hBlendOutMinimum = (float)heightBlendPoints[i * 2 - 2];
                                hBlendOutMaximum = (float)heightBlendPoints[i * 2 - 1];
                            }
                            hValue = heightMapData[Px, Py];
                            if (hValue >= hBlendInMaximum && hValue <= hBlendOutMinimum)
                            {
                                // Full...
                                hBlended = 1;
                            } else if (hValue >= hBlendInMinimum && hValue < hBlendInMaximum)
                            {
                                // Blend in...
                                hBlended = (hValue - hBlendInMinimum) / (hBlendInMaximum - hBlendInMinimum);
                            } else if (hValue > hBlendOutMinimum && hValue <= hBlendOutMaximum)
                            {
                                // Blend out...
                                hBlended = 1 - ((hValue - hBlendOutMinimum) / (hBlendOutMaximum - hBlendOutMinimum));
                            }
                            // Subtract slope...
                            float sValue = slopeMapData[Px, Py];
                            hBlended -= sValue;
                            if (hBlended < 0)
                            {
                                hBlended = 0;
                            }
                            splatMapData[Px, Py, i] = hBlended;
                        }
                    }
                }
                // Generate splat maps...
                textureProgressDelegate("Procedural Terrain Texture", "Generating splat map. Please wait.", 0.9f);
                terData.SetAlphamaps(0, 0, splatMapData);
                // Clean up...
                heightMapData = null;
                slopeMapData = null;
                splatMapData = null;
            }
            catch (Exception e)
            {
                // Clean up...
                heightMapData = null;
                slopeMapData = null;
                splatMapData = null;
                Debug.LogError("An error occurred: " + e);
            }
        }

        public void addSplatPrototype(Texture2D tex, int index)
        {
            SplatPrototype[] newSplatPrototypes = new SplatPrototype[index + 1];
            for (int i = 0; i <= index; i++)
            {
                newSplatPrototypes[i] = new SplatPrototype();
                if (i == index)
                {
                    newSplatPrototypes[i].texture = tex;
                    newSplatPrototypes[i].tileSize = new Vector2(15, 15);
                } else
                {
                    newSplatPrototypes[i].texture = splatPrototypes[i].texture;
                    newSplatPrototypes[i].tileSize = splatPrototypes[i].tileSize;
                }
            }
            splatPrototypes = newSplatPrototypes;
            if (index + 1 > 2)
            {
                addBlendPoints();
            }
        }

        public void deleteSplatPrototype(Texture2D tex, int index)
        {
            int c = 0;
            c = splatPrototypes.Length;
            SplatPrototype[] newSplatPrototypes = new SplatPrototype[c - 1];
            int n = 0;
            for (int i = 0; i < c; i++)
            {
                if (i != index)
                {
                    newSplatPrototypes[n] = new SplatPrototype();
                    newSplatPrototypes[n].texture = splatPrototypes[i].texture;
                    newSplatPrototypes[n].tileSize = splatPrototypes[i].tileSize;
                    n++;
                }
            }
            splatPrototypes = newSplatPrototypes;
            if (c - 1 > 1)
            {
                deleteBlendPoints();
            }
        }

        public void deleteAllSplatPrototypes()
        {
            SplatPrototype[] newSplatPrototypes = new SplatPrototype[0];
            splatPrototypes = newSplatPrototypes;
        }

        public void addBlendPoints()
        {
            float lastBlendPoint = 0.0f;
            if (heightBlendPoints.Count > 0)
            {
                lastBlendPoint = (float)heightBlendPoints[heightBlendPoints.Count - 1];
            }
            float newBlendPoint = lastBlendPoint + (1.0f - lastBlendPoint) * 0.33f;
            heightBlendPoints.Add(newBlendPoint);
            newBlendPoint = lastBlendPoint + (1.0f - lastBlendPoint) * 0.66f;
            heightBlendPoints.Add(newBlendPoint);
        }

        public void deleteBlendPoints()
        {
            if (heightBlendPoints.Count > 0)
            {
                heightBlendPoints.RemoveAt(heightBlendPoints.Count - 1);
            }
            if (heightBlendPoints.Count > 0)
            {
                heightBlendPoints.RemoveAt(heightBlendPoints.Count - 1);
            }
        }

        public void deleteAllBlendPoints()
        {
            heightBlendPoints = new List<float>();
        }

        // -------------------------------------------------------------------------------------------------------- GENERATOR FILTERS

        public void generateTerrain(GeneratorProgressDelegate generatorProgressDelegate)
        {
            // Check enum vars...
            convertIntVarsToEnums();
            // Error checking...
            Terrain ter = (Terrain)GetComponent(typeof(Terrain));
            if (ter == null)
            {
                return;
            }
            TerrainData terData = ter.terrainData;
            int Tw = terData.heightmapWidth;
            int Th = terData.heightmapHeight;
            float[,] heightMap = terData.GetHeights(0, 0, Tw, Th);
            float[,] generatedHeightMap = (float[,])heightMap.Clone();
            // Set the number of iterations and pass the height array to the appropriate generator script...
            switch (generatorType)
            {
                case GeneratorType.Voronoi:
                    generatedHeightMap = generateVoronoi(generatedHeightMap, new Vector2(Tw, Th), generatorProgressDelegate);
                    break;
                case GeneratorType.DiamondSquare:
                    generatedHeightMap = generateDiamondSquare(generatedHeightMap, new Vector2(Tw, Th), generatorProgressDelegate);
                    break;
                case GeneratorType.Perlin:
                    generatedHeightMap = generatePerlin(generatedHeightMap, new Vector2(Tw, Th), generatorProgressDelegate);
                    break;
                case GeneratorType.Smooth:
                    generatedHeightMap = smooth(generatedHeightMap, new Vector2(Tw, Th), generatorProgressDelegate);
                    break;
                case GeneratorType.Normalise:
                    generatedHeightMap = normalise(generatedHeightMap, new Vector2(Tw, Th), generatorProgressDelegate);
                    break;
                default:
                    return;
            }
            // Apply it to the terrain object...
            for (int Ty = 0; Ty < Th; Ty++)
            {
                for (int Tx = 0; Tx < Tw; Tx++)
                {
                    float oldHeightAtPoint = heightMap[Tx, Ty];
                    float newHeightAtPoint = generatedHeightMap[Tx, Ty];
                    float blendedHeightAtPoint = 0.0f;
                    switch (generatorType)
                    {
                        case GeneratorType.Voronoi:
                            blendedHeightAtPoint = (newHeightAtPoint * voronoiBlend) + (oldHeightAtPoint * (1.0f - voronoiBlend));
                            break;
                        case GeneratorType.DiamondSquare:
                            blendedHeightAtPoint = (newHeightAtPoint * diamondSquareBlend) + (oldHeightAtPoint * (1.0f - diamondSquareBlend));
                            break;
                        case GeneratorType.Perlin:
                            blendedHeightAtPoint = (newHeightAtPoint * perlinBlend) + (oldHeightAtPoint * (1.0f - perlinBlend));
                            break;
                        case GeneratorType.Smooth:
                            blendedHeightAtPoint = (newHeightAtPoint * smoothBlend) + (oldHeightAtPoint * (1.0f - smoothBlend));
                            break;
                        case GeneratorType.Normalise:
                            blendedHeightAtPoint = (newHeightAtPoint * normaliseBlend) + (oldHeightAtPoint * (1.0f - normaliseBlend));
                            break;
                    }
                    heightMap[Tx, Ty] = blendedHeightAtPoint;
                }
            }
            terData.SetHeights(0, 0, heightMap);
        }

        private float[,] generateVoronoi(float[,] heightMap, Vector2 arraySize, GeneratorProgressDelegate generatorProgressDelegate)
        {
            int Tx = (int)arraySize.x;
            int Ty = (int)arraySize.y;
            // Create Voronoi set...
            ArrayList voronoiSet = new ArrayList();
            int i;
            for (i = 0; i < voronoiCells; i++)
            {
                Peak newPeak = new Peak();
                int xCoord = (int)Mathf.Floor(UnityEngine.Random.value * Tx);
                int yCoord = (int)Mathf.Floor(UnityEngine.Random.value * Ty);
                float pointHeight = UnityEngine.Random.value;
                if (UnityEngine.Random.value > voronoiFeatures)
                {
                    pointHeight = 0.0f;
                }
                newPeak.peakPoint = new Vector2(xCoord, yCoord);
                newPeak.peakHeight = pointHeight;
                voronoiSet.Add(newPeak);
            }
            int Mx;
            int My;
            float highestScore = 0.0f;
            for (My = 0; My < Ty; My++)
            {
                for (Mx = 0; Mx < Tx; Mx++)
                {
                    ArrayList peakDistances = new ArrayList();
                    for (i = 0; i < voronoiCells; i++)
                    {
                        Peak peakI = (Peak)voronoiSet[i];
                        Vector2 peakPoint = peakI.peakPoint;
                        float distanceToPeak = Vector2.Distance(peakPoint, new Vector2(Mx, My));
                        PeakDistance newPeakDistance = new PeakDistance();
                        newPeakDistance.id = i;
                        newPeakDistance.dist = distanceToPeak;
                        peakDistances.Add(newPeakDistance);
                    }
                    peakDistances.Sort();
                    PeakDistance peakDistOne = (PeakDistance)peakDistances[0];
                    PeakDistance peakDistTwo = (PeakDistance)peakDistances[1];
                    int p1 = peakDistOne.id;
                    float d1 = peakDistOne.dist;
                    float d2 = peakDistTwo.dist;
                    float scale = Mathf.Abs(d1 - d2) / ((Tx + Ty) / Mathf.Sqrt(voronoiCells));
                    Peak peakOne = (Peak)voronoiSet[p1];
                    float h1 = (float)peakOne.peakHeight;
                    float hScore = h1 - Mathf.Abs(d1 / d2) * h1;
                    float asRadians;
                    switch (voronoiType)
                    {
                        case VoronoiType.Linear:
                            // Nothing...
                            break;
                        case VoronoiType.Sine:
                            asRadians = hScore * Mathf.PI - Mathf.PI / 2;
                            hScore = 0.5f + Mathf.Sin(asRadians) / 2;
                            break;
                        case VoronoiType.Tangent:
                            asRadians = hScore * Mathf.PI / 2;
                            hScore = 0.5f + Mathf.Tan(asRadians) / 2;
                            break;
                    }
                    hScore = (hScore * scale * voronoiScale) + (hScore * (1.0f - voronoiScale));
                    if (hScore < 0.0f)
                    {
                        hScore = 0.0f;
                    } else if (hScore > 1.0f)
                    {
                        hScore = 1.0f;
                    }
                    heightMap[Mx, My] = hScore;
                    if (hScore > highestScore)
                    {
                        highestScore = hScore;
                    }
                }
                // Show progress...
                float completePoints = My * Ty;
                float totalPoints = Tx * Ty;
                float percentComplete = completePoints / totalPoints;
                generatorProgressDelegate("Voronoi Generator", "Generating height map. Please wait.", percentComplete);
            }
            // Normalise...
            for (My = 0; My < Ty; My++)
            {
                for (Mx = 0; Mx < Tx; Mx++)
                {
                    float normalisedHeight = heightMap[Mx, My] * (1.0f / highestScore);
                    heightMap[Mx, My] = normalisedHeight;
                }
            }
            return heightMap;
        }

        private float[,] generateDiamondSquare(float[,] heightMap, Vector2 arraySize, GeneratorProgressDelegate generatorProgressDelegate)
        {
            int Tw = (int)arraySize.x;
            int Th = (int)arraySize.y;
            float heightRange = 1.0f;
            int step = Tw - 1;
            heightMap[0, 0] = 0.5f;
            heightMap[Tw - 1, 0] = 0.5f;
            heightMap[0, Th - 1] = 0.5f;
            heightMap[Tw - 1, Th - 1] = 0.5f;
            generatorProgressDelegate("Fractal Generator", "Generating height map. Please wait.", 0.0f);
            while (step > 1)
            {
                // diamond
                for (int Tx = 0; Tx < Tw - 1; Tx += step)
                {
                    for (int Ty = 0; Ty < Th - 1; Ty += step)
                    {
                        int sx = Tx + (step >> 1);
                        int sy = Ty + (step >> 1);
                        Vector2[] points = new Vector2[4];
                        points[0] = new Vector2(Tx, Ty);
                        points[1] = new Vector2(Tx + step, Ty);
                        points[2] = new Vector2(Tx, Ty + step);
                        points[3] = new Vector2(Tx + step, Ty + step);
                        dsCalculateHeight(heightMap, arraySize, sx, sy, points, heightRange);
                    }
                }
                // square
                for (int Tx = 0; Tx < Tw - 1; Tx += step)
                {
                    for (int Ty = 0; Ty < Th - 1; Ty += step)
                    {
                        int halfstep = step >> 1;
                        int x1 = Tx + halfstep;
                        int y1 = Ty;
                        int x2 = Tx;
                        int y2 = Ty + halfstep;
                        Vector2[] points1 = new Vector2[4];
                        points1[0] = new Vector2(x1 - halfstep, y1);
                        points1[1] = new Vector2(x1, y1 - halfstep);
                        points1[2] = new Vector2(x1 + halfstep, y1);
                        points1[3] = new Vector2(x1, y1 + halfstep);
                        Vector2[] points2 = new Vector2[4];
                        points2[0] = new Vector2(x2 - halfstep, y2);
                        points2[1] = new Vector2(x2, y2 - halfstep);
                        points2[2] = new Vector2(x2 + halfstep, y2);
                        points2[3] = new Vector2(x2, y2 + halfstep);
                        dsCalculateHeight(heightMap, arraySize, x1, y1, points1, heightRange);
                        dsCalculateHeight(heightMap, arraySize, x2, y2, points2, heightRange);
                    }
                }
                heightRange *= diamondSquareDelta;
                step >>= 1;
            }
            generatorProgressDelegate("Fractal Generator", "Generating height map. Please wait.", 1.0f);
            return heightMap;
        }

        private void dsCalculateHeight(float[,] heightMap, Vector2 arraySize, int Tx, int Ty, Vector2[] points, float heightRange)
        {
            int Tw = (int)arraySize.x;
            int Th = (int)arraySize.y;
            float h = 0.0f;
            for (int i = 0; i < 4; i++)
            {
                if (points[i].x < 0)
                {
                    points[i].x += (Tw - 1);
                } else if (points[i].x > Tw)
                {
                    points[i].x -= (Tw - 1);
                } else if (points[i].y < 0)
                {
                    points[i].y += Th - 1;
                } else if (points[i].y > Th)
                {
                    points[i].y -= Th - 1;
                }
                h += (float)(heightMap[(int)points[i].x, (int)points[i].y] / 4);
            }
            h += (UnityEngine.Random.value * heightRange - heightRange / 2);
            if (h < 0.0f)
            {
                h = 0.0f;
            } else if (h > 1.0f)
            {
                h = 1.0f;
            }
            heightMap[Tx, Ty] = h;
            if (Tx == 0)
            {
                heightMap[Tw - 1, Ty] = h;
            } else if (Tx == Tw - 1)
            {
                heightMap[0, Ty] = h;
            } else if (Ty == 0)
            {
                heightMap[Tx, Th - 1] = h;
            } else if (Ty == Th - 1)
            {
                heightMap[Tx, 0] = h;
            }
        }

        private float[,] generatePerlin(float[,] heightMap, Vector2 arraySize, GeneratorProgressDelegate generatorProgressDelegate)
        {
            int Tw = (int)arraySize.x;
            int Th = (int)arraySize.y;
            // Zero...
            for (int My = 0; My < Th; My++)
            {
                for (int Mx = 0; Mx < Tw; Mx++)
                {
                    heightMap[Mx, My] = 0.0f;
                }
            }
            PerlinNoise2D[] noiseFunctions = new PerlinNoise2D[perlinOctaves];
            int freq = perlinFrequency;
            float amp = 1.0f;
            int i;
            for (i = 0; i < perlinOctaves; i++)
            {
                noiseFunctions[i] = new PerlinNoise2D(freq, amp);
                freq *= 2;
                amp /= 2;
            }
            for (i = 0; i < perlinOctaves; i++)
            {
                double xStep = (float)Tw / (float)noiseFunctions[i].Frequency;
                double yStep = (float)Th / (float)noiseFunctions[i].Frequency;
                for (int Px = 0; Px < Tw; Px++)
                {
                    for (int Py = 0; Py < Th; Py++)
                    {
                        int Pa = (int)(Px / xStep);
                        int Pb = Pa + 1;
                        int Pc = (int)(Py / yStep);
                        int Pd = Pc + 1;
                        double interpValue = noiseFunctions[i].getInterpolatedPoint(Pa, Pb, Pc, Pd, (Px / xStep) - Pa, (Py / yStep) - Pc);
                        heightMap[Px, Py] += (float)(interpValue * noiseFunctions[i].Amplitude);
                    }
                }
                // Show progress...
                float percentComplete = (i + 1) / perlinOctaves;
                generatorProgressDelegate("Perlin Generator", "Generating height map. Please wait.", percentComplete);
            }
            GeneratorProgressDelegate normaliseProgressDelegate = new GeneratorProgressDelegate(dummyGeneratorProgress);
            float oldNormaliseMin = normaliseMin;
            float oldNormaliseMax = normaliseMax;
            float oldNormaliseBlend = normaliseBlend;
            normaliseMin = 0.0f;
            normaliseMax = 1.0f;
            normaliseBlend = 1.0f;
            heightMap = normalise(heightMap, arraySize, normaliseProgressDelegate);
            normaliseMin = oldNormaliseMin;
            normaliseMax = oldNormaliseMax;
            normaliseBlend = oldNormaliseBlend;
            for (int Px = 0; Px < Tw; Px++)
            {
                for (int Py = 0; Py < Th; Py++)
                {
                    heightMap[Px, Py] = heightMap[Px, Py] * perlinAmplitude;
                }
            }
            for (i = 0; i < perlinOctaves; i++)
            {
                noiseFunctions[i] = null;
            }
            noiseFunctions = null;
            return heightMap;
        }

        public class PerlinNoise2D
        {
            private double[,] noiseValues;
            private float amplitude = 1.0f;
            private int frequency = 1;

            public PerlinNoise2D(int freq, float _amp)
            {
                System.Random rand = new System.Random(System.Environment.TickCount);
                noiseValues = new double[freq, freq];
                amplitude = _amp;
                frequency = freq;
                for (int i = 0; i < freq; i++)
                {
                    for (int j = 0; j < freq; j++)
                    {
                        noiseValues[i, j] = rand.NextDouble();
                    }
                }
            }

            public double getInterpolatedPoint(int _xa, int _xb, int _ya, int _yb, double Px, double Py)
            {
                double i1 = interpolate(noiseValues[_xa % Frequency, _ya % frequency], noiseValues[_xb % Frequency, _ya % frequency], Px);
                double i2 = interpolate(noiseValues[_xa % Frequency, _yb % frequency], noiseValues[_xb % Frequency, _yb % frequency], Px);
                return interpolate(i1, i2, Py);
            }

            private double interpolate(double Pa, double Pb, double Px)
            {
                double ft = Px * Mathf.PI;
                double f = (1 - Mathf.Cos((float)ft)) * 0.5;
                return Pa * (1 - f) + Pb * f;
            }

            public float Amplitude
            {
                get
                {
                    return amplitude;
                }
            }

            public int Frequency
            {
                get
                {
                    return frequency;
                }
            }
        }

        private float[,] smooth(float[,] heightMap, Vector2 arraySize, GeneratorProgressDelegate generatorProgressDelegate)
        {
            int Tw = (int)arraySize.x;
            int Th = (int)arraySize.y;
            int xNeighbours;
            int yNeighbours;
            int xShift;
            int yShift;
            int xIndex;
            int yIndex;
            int Tx;
            int Ty;
            // Start iterations...
            for (int iter = 0; iter < smoothIterations; iter++)
            {
                for (Ty = 0; Ty < Th; Ty++)
                {
                    // y...
                    if (Ty == 0)
                    {
                        yNeighbours = 2;
                        yShift = 0;
                        yIndex = 0;
                    } else if (Ty == Th - 1)
                    {
                        yNeighbours = 2;
                        yShift = -1;
                        yIndex = 1;
                    } else
                    {
                        yNeighbours = 3;
                        yShift = -1;
                        yIndex = 1;
                    }
                    for (Tx = 0; Tx < Tw; Tx++)
                    {
                        // x...
                        if (Tx == 0)
                        {
                            xNeighbours = 2;
                            xShift = 0;
                            xIndex = 0;
                        } else if (Tx == Tw - 1)
                        {
                            xNeighbours = 2;
                            xShift = -1;
                            xIndex = 1;
                        } else
                        {
                            xNeighbours = 3;
                            xShift = -1;
                            xIndex = 1;
                        }
                        int Ny;
                        int Nx;
                        float hCumulative = 0.0f;
                        int nNeighbours = 0;
                        for (Ny = 0; Ny < yNeighbours; Ny++)
                        {
                            for (Nx = 0; Nx < xNeighbours; Nx++)
                            {
                                if (neighbourhood == Neighbourhood.Moore || (neighbourhood == Neighbourhood.VonNeumann && (Nx == xIndex || Ny == yIndex)))
                                {
                                    float heightAtPoint = heightMap[Tx + Nx + xShift, Ty + Ny + yShift]; // Get height at point
                                    hCumulative += heightAtPoint;
                                    nNeighbours++;
                                }
                            }
                        }
                        float hAverage = hCumulative / nNeighbours;
                        heightMap[Tx + xIndex + xShift, Ty + yIndex + yShift] = hAverage;
                    }
                }
                // Show progress...
                float percentComplete = (iter + 1) / smoothIterations;
                generatorProgressDelegate("Smoothing Filter", "Smoothing height map. Please wait.", percentComplete);
            }
            return heightMap;
        }

        private float[,] normalise(float[,] heightMap, Vector2 arraySize, GeneratorProgressDelegate generatorProgressDelegate)
        {
            int Tx = (int)arraySize.x;
            int Ty = (int)arraySize.y;
            int Mx;
            int My;
            float highestPoint = 0.0f;
            float lowestPoint = 1.0f;
            generatorProgressDelegate("Normalise Filter", "Normalising height map. Please wait.", 0.0f);
            // Find highest and lowest points...
            for (My = 0; My < Ty; My++)
            {
                for (Mx = 0; Mx < Tx; Mx++)
                {
                    float heightAtPoint = heightMap[Mx, My];
                    if (heightAtPoint < lowestPoint)
                    {
                        lowestPoint = heightAtPoint;
                    } else if (heightAtPoint > highestPoint)
                    {
                        highestPoint = heightAtPoint;
                    }
                }
            }
            generatorProgressDelegate("Normalise Filter", "Normalising height map. Please wait.", 0.5f);
            // Normalise...
            float heightRange = highestPoint - lowestPoint;
            float normalisedHeightRange = normaliseMax - normaliseMin;
            for (My = 0; My < Ty; My++)
            {
                for (Mx = 0; Mx < Tx; Mx++)
                {
                    float normalisedHeight = ((heightMap[Mx, My] - lowestPoint) / heightRange) * normalisedHeightRange;
                    heightMap[Mx, My] = normaliseMin + normalisedHeight;
                }
            }
            generatorProgressDelegate("Normalise Filter", "Normalising height map. Please wait.", 1.0f);
            return heightMap;
        }

        // -------------------------------------------------------------------------------------------------------- API FUNCTIONS

        public void FastThermalErosion(int iterations, float minSlope, float blendAmount)
        {
            erosionTypeInt = 0;
            erosionType = ErosionType.Thermal;
            thermalIterations = iterations;
            thermalMinSlope = minSlope;
            thermalFalloff = blendAmount;
            neighbourhood = Neighbourhood.Moore;
            ErosionProgressDelegate erosionProgressDelegate = new ErosionProgressDelegate(dummyErosionProgress);
            erodeAllTerrain(erosionProgressDelegate);
        }

        public void FastHydraulicErosion(int iterations, float maxSlope, float blendAmount)
        {
            erosionTypeInt = 1;
            erosionType = ErosionType.Hydraulic;
            hydraulicTypeInt = 0;
            hydraulicType = HydraulicType.Fast;
            hydraulicIterations = iterations;
            hydraulicMaxSlope = maxSlope;
            hydraulicFalloff = blendAmount;
            neighbourhood = Neighbourhood.Moore;
            ErosionProgressDelegate erosionProgressDelegate = new ErosionProgressDelegate(dummyErosionProgress);
            erodeAllTerrain(erosionProgressDelegate);
        }

        public void FullHydraulicErosion(int iterations, float rainfall, float evaporation, float solubility, float saturation)
        {
            erosionTypeInt = 1;
            erosionType = ErosionType.Hydraulic;
            hydraulicTypeInt = 1;
            hydraulicType = HydraulicType.Full;
            hydraulicIterations = iterations;
            hydraulicRainfall = rainfall;
            hydraulicEvaporation = evaporation;
            hydraulicSedimentSolubility = solubility;
            hydraulicSedimentSaturation = saturation;
            neighbourhood = Neighbourhood.Moore;
            ErosionProgressDelegate erosionProgressDelegate = new ErosionProgressDelegate(dummyErosionProgress);
            erodeAllTerrain(erosionProgressDelegate);
        }

        public void VelocityHydraulicErosion(int iterations, float rainfall, float evaporation, float solubility, float saturation, float velocity, float momentum, float entropy, float downcutting)
        {
            erosionTypeInt = 1;
            erosionType = ErosionType.Hydraulic;
            hydraulicTypeInt = 2;
            hydraulicType = HydraulicType.Velocity;
            hydraulicIterations = iterations;
            hydraulicVelocityRainfall = rainfall;
            hydraulicVelocityEvaporation = evaporation;
            hydraulicVelocitySedimentSolubility = solubility;
            hydraulicVelocitySedimentSaturation = saturation;
            hydraulicVelocity = velocity;
            hydraulicMomentum = momentum;
            hydraulicEntropy = entropy;
            hydraulicDowncutting = downcutting;
            neighbourhood = Neighbourhood.Moore;
            ErosionProgressDelegate erosionProgressDelegate = new ErosionProgressDelegate(dummyErosionProgress);
            erodeAllTerrain(erosionProgressDelegate);
        }

        public void TidalErosion(int iterations, float seaLevel, float tidalRange, float cliffLimit)
        {
            erosionTypeInt = 2;
            erosionType = ErosionType.Tidal;
            tidalIterations = iterations;
            tidalSeaLevel = seaLevel;
            tidalRangeAmount = tidalRange;
            tidalCliffLimit = cliffLimit;
            neighbourhood = Neighbourhood.Moore;
            ErosionProgressDelegate erosionProgressDelegate = new ErosionProgressDelegate(dummyErosionProgress);
            erodeAllTerrain(erosionProgressDelegate);
        }

        public void WindErosion(int iterations, float direction, float force, float lift, float gravity, float capacity, float entropy, float smoothing)
        {
            erosionTypeInt = 3;
            erosionType = ErosionType.Wind;
            windIterations = iterations;
            windDirection = direction;
            windForce = force;
            windLift = lift;
            windGravity = gravity;
            windCapacity = capacity;
            windEntropy = entropy;
            windSmoothing = smoothing;
            neighbourhood = Neighbourhood.Moore;
            ErosionProgressDelegate erosionProgressDelegate = new ErosionProgressDelegate(dummyErosionProgress);
            erodeAllTerrain(erosionProgressDelegate);
        }

        public void TextureTerrain(float[] slopeStops, float[] heightStops, Texture2D[] textures)
        {
            if (slopeStops.Length != 2)
            {
                Debug.LogError("Error: slopeStops must have 2 values");
                return;
            }
            if (heightStops.Length > 8)
            {
                Debug.LogError("Error: heightStops must have no more than 8 values");
                return;
            }
            if (heightStops.Length % 2 != 0)
            {
                Debug.LogError("Error: heightStops must have an even number of values");
                return;
            }
            int numTextures = textures.Length;
            int numTexturesByStops = (heightStops.Length / 2) + 2;
            if (numTextures != numTexturesByStops)
            {
                Debug.LogError("Error: heightStops contains an incorrect number of values");
                return;
            }
            foreach (float stop in slopeStops)
            {
                if (stop < 0 || stop > 90)
                {
                    Debug.LogError("Error: The value of all slopeStops must be in the range 0.0 to 90.0");
                    return;
                }
            }
            foreach (float stop in heightStops)
            {
                if (stop < 0 || stop > 1)
                {
                    Debug.LogError("Error: The value of all heightStops must be in the range 0.0 to 1.0");
                    return;
                }
            }
            Terrain ter = (Terrain)GetComponent(typeof(Terrain));
            TerrainData terData = ter.terrainData;
            splatPrototypes = terData.splatPrototypes;
            deleteAllSplatPrototypes();
            int n = 0;
            foreach (Texture2D tex in textures)
            {
                addSplatPrototype(tex, n);
                n++;
            }
            slopeBlendMinAngle = slopeStops[0];
            slopeBlendMaxAngle = slopeStops[1];
            n = 0;
            foreach (float stop in heightStops)
            {
                heightBlendPoints[n] = stop;
                n++;
            }
            terData.splatPrototypes = splatPrototypes;
            TextureProgressDelegate textureProgressDelegate = new TextureProgressDelegate(dummyTextureProgress);
            textureTerrain(textureProgressDelegate);
        }

        public void VoronoiGenerator(FeatureType featureType, int cells, float features, float scale, float blend)
        {
            generatorTypeInt = 0;
            generatorType = GeneratorType.Voronoi;
            switch (featureType)
            {
                case FeatureType.Mountains:
                    voronoiTypeInt = 0;
                    voronoiType = VoronoiType.Linear;
                    break;
                case FeatureType.Hills:
                    voronoiTypeInt = 1;
                    voronoiType = VoronoiType.Sine;
                    break;
                case FeatureType.Plateaus:
                    voronoiTypeInt = 2;
                    voronoiType = VoronoiType.Tangent;
                    break;
            }
            voronoiCells = cells;
            voronoiFeatures = features;
            voronoiScale = scale;
            voronoiBlend = blend;
            GeneratorProgressDelegate generatorProgressDelegate = new GeneratorProgressDelegate(dummyGeneratorProgress);
            generateTerrain(generatorProgressDelegate);
        }

        public void FractalGenerator(float fractalDelta, float blend)
        {
            generatorTypeInt = 1;
            generatorType = GeneratorType.DiamondSquare;
            diamondSquareDelta = fractalDelta;
            diamondSquareBlend = blend;
            GeneratorProgressDelegate generatorProgressDelegate = new GeneratorProgressDelegate(dummyGeneratorProgress);
            generateTerrain(generatorProgressDelegate);
        }

        public void PerlinGenerator(int frequency, float amplitude, int octaves, float blend)
        {
            generatorTypeInt = 2;
            generatorType = GeneratorType.Perlin;
            perlinFrequency = frequency;
            perlinAmplitude = amplitude;
            perlinOctaves = octaves;
            perlinBlend = blend;
            GeneratorProgressDelegate generatorProgressDelegate = new GeneratorProgressDelegate(dummyGeneratorProgress);
            generateTerrain(generatorProgressDelegate);
        }

        public void SmoothTerrain(int iterations, float blend)
        {
            generatorTypeInt = 3;
            generatorType = GeneratorType.Smooth;
            smoothIterations = iterations;
            smoothBlend = blend;
            GeneratorProgressDelegate generatorProgressDelegate = new GeneratorProgressDelegate(dummyGeneratorProgress);
            generateTerrain(generatorProgressDelegate);
        }

        public void NormaliseTerrain(float minHeight, float maxHeight, float blend)
        {
            generatorTypeInt = 4;
            generatorType = GeneratorType.Normalise;
            normaliseMin = minHeight;
            normaliseMax = maxHeight;
            normaliseBlend = blend;
            GeneratorProgressDelegate generatorProgressDelegate = new GeneratorProgressDelegate(dummyGeneratorProgress);
            generateTerrain(generatorProgressDelegate);
        }

        public void NormalizeTerrain(float minHeight, float maxHeight, float blend)
        {
            NormaliseTerrain(minHeight, maxHeight, blend);
        }

        // -------------------------------------------------------------------------------------------------------- UTILITIES

        private void convertIntVarsToEnums()
        {
            switch (erosionTypeInt)
            {
                case 0:
                    erosionType = ErosionType.Thermal;
                    break;
                case 1:
                    erosionType = ErosionType.Hydraulic;
                    break;
                case 2:
                    erosionType = ErosionType.Tidal;
                    break;
                case 3:
                    erosionType = ErosionType.Wind;
                    break;
                case 4:
                    erosionType = ErosionType.Glacial;
                    break;
            }
            switch (hydraulicTypeInt)
            {
                case 0:
                    hydraulicType = HydraulicType.Fast;
                    break;
                case 1:
                    hydraulicType = HydraulicType.Full;
                    break;
                case 2:
                    hydraulicType = HydraulicType.Velocity;
                    break;
            }
            switch (generatorTypeInt)
            {
                case 0:
                    generatorType = GeneratorType.Voronoi;
                    break;
                case 1:
                    generatorType = GeneratorType.DiamondSquare;
                    break;
                case 2:
                    generatorType = GeneratorType.Perlin;
                    break;
                case 3:
                    generatorType = GeneratorType.Smooth;
                    break;
                case 4:
                    generatorType = GeneratorType.Normalise;
                    break;
            }
            switch (voronoiTypeInt)
            {
                case 0:
                    voronoiType = VoronoiType.Linear;
                    break;
                case 1:
                    voronoiType = VoronoiType.Sine;
                    break;
                case 2:
                    voronoiType = VoronoiType.Tangent;
                    break;
            }
            switch (neighbourhoodInt)
            {
                case 0:
                    neighbourhood = Neighbourhood.Moore;
                    break;
                case 1:
                    neighbourhood = Neighbourhood.VonNeumann;
                    break;
            }
        }

        // -------------------------------------------------------------------------------------------------------- DUMMY DELEGATE VERSION

        public void dummyErosionProgress(string titleString, string displayString, int iteration, int nIterations, float percentComplete)
        {
            //
        }

        public void dummyTextureProgress(string titleString, string displayString, float percentComplete)
        {
            //
        }

        public void dummyGeneratorProgress(string titleString, string displayString, float percentComplete)
        {
            //
        }

        // -------------------------------------------------------------------------------------------------------- END
    }
}