Beispiel #1
0
 private void OnTransform(LE_EObjectEditMode p_editMode, Transform p_transform)
 {
     if (LE_EventInterface.OnChangeLevelData != null)
     {
         LE_EventInterface.OnChangeLevelData(m_editHandle, new LE_LevelDataChangedEvent(LE_ELevelDataChangeType.OBJECT_TRANSFORM));
     }
 }
        private bool Apply(float p_direction)
        {
            if (m_terrainMgr == null || m_heightsDelta == null)
            {
                Debug.LogError("LE_CmdChangeTerrainHeight: Apply: could not execute, m_terrainMgr or m_heightsDelta are null!");
                return(false);
            }

            int xBase  = m_heightsDelta.m_xBase;
            int yBase  = m_heightsDelta.m_yBase;
            int width  = m_heightsDelta.m_heights.GetLength(1);
            int height = m_heightsDelta.m_heights.GetLength(0);

            if (width > m_terrainMgr.TerrainData.heightmapWidth || height > m_terrainMgr.TerrainData.heightmapHeight)
            {
                Debug.LogError("LE_CmdChangeTerrainHeight: Apply: could not execute, terrain height map resolution was reduced in the meantime!");
                return(false);
            }

            float[,] dataAfterChange = m_terrainMgr.TerrainData.GetHeights(xBase, yBase, width, height);
            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    dataAfterChange[y, x] += p_direction * m_heightsDelta.m_heights[y, x];
                }
            }
            m_terrainMgr.TerrainData.SetHeights(m_heightsDelta.m_xBase, m_heightsDelta.m_yBase, dataAfterChange);
            // notify listeners that the level data was changed
            if (LE_EventInterface.OnChangeLevelData != null)
            {
                LE_EventInterface.OnChangeLevelData(m_terrainMgr, new LE_LevelDataChangedEvent(LE_ELevelDataChangeType.TERRAIN_HEIGHTS));
            }
            return(true);
        }
Beispiel #3
0
 public void AddTerrainTexture(Texture2D p_texture)
 {
     if (m_confT.TerrainTextureConfig != null)
     {
         if (m_GUI3dTerrain != null && m_GUI3dTerrain.TerrainManager != null && m_GUI3dTerrain.TerrainManager.TerrainData != null)
         {
             SplatPrototype[] splatTexturesOld = m_GUI3dTerrain.TerrainManager.TerrainData.splatPrototypes;
             SplatPrototype[] splatTexturesNew = new SplatPrototype[splatTexturesOld.Length + 1];
             System.Array.Copy(splatTexturesOld, splatTexturesNew, splatTexturesOld.Length);
             int textureIndex = GetTextureIndex(p_texture);
             if (textureIndex >= 0)
             {
                 splatTexturesNew[splatTexturesNew.Length - 1]             = new SplatPrototype();
                 splatTexturesNew[splatTexturesNew.Length - 1].texture     = m_confT.TerrainTextureConfig.TERRAIN_TEXTURES[textureIndex];
                 splatTexturesNew[splatTexturesNew.Length - 1].tileSize    = m_confT.TerrainTextureConfig.TERRAIN_TEXTURE_SIZES[textureIndex];
                 splatTexturesNew[splatTexturesNew.Length - 1].tileOffset  = m_confT.TerrainTextureConfig.TERRAIN_TEXTURE_OFFSETS[textureIndex];
                 m_GUI3dTerrain.TerrainManager.TerrainData.splatPrototypes = splatTexturesNew;
                 m_doRebuildTerrainTab = true;
                 // notify listeners that the level data was changed
                 if (LE_EventInterface.OnChangeLevelData != null)
                 {
                     LE_EventInterface.OnChangeLevelData(this, new LE_LevelDataChangedEvent(LE_ELevelDataChangeType.TERRAIN_TEXTURES));
                 }
             }
             else
             {
                 Debug.LogError("LE_LogicTerrain: AddTerrainTexture: could not find given texture in TerrainTextureConfig!");
             }
         }
     }
     else
     {
         Debug.LogError("LE_LogicTerrain: AddTerrainTexture: LE_ConfigTerrain has no TerrainTextureConfig set!");
     }
 }
Beispiel #4
0
        public void ChangeHeight(float p_delta, float p_targetHeight, Texture2D p_alphaBrushTexture, float p_relativeBrushSize, Vector2 p_relativeLocalLocation)
        {
            float[,] heights;
            int   minX, maxX, minY, maxY;
            float relBrushMinX, relBrushMinY, heightmapMaxX, heightmapMaxY;

            ChangeHeightInternal(p_alphaBrushTexture, p_relativeBrushSize, p_relativeLocalLocation,
                                 out minX, out maxX, out minY, out maxY, out heights,
                                 out relBrushMinX, out relBrushMinY, out heightmapMaxX, out heightmapMaxY);

            // apply height change to every affected heights array entry
            // according to given p_delta and the alpha value in p_alphaBrushTexture
            // height is always changed towards p_targetHeight
            int iterateToX = Mathf.Min(maxX - minX, heights.GetLength(0) - 1);
            int iterateToY = Mathf.Min(maxY - minY, heights.GetLength(1) - 1);

            for (int indexX = 0; indexX <= iterateToX; indexX++)
            {
                for (int indexY = 0; indexY <= iterateToY; indexY++)
                {
                    float height = heights[indexX, indexY];
                    if (Mathf.Abs(height - p_targetHeight) > 0.0001f)
                    {
                        float v               = (((float)(indexX + minX) / heightmapMaxX) - relBrushMinX) / p_relativeBrushSize;
                        float u               = (((float)(indexY + minY) / heightmapMaxY) - relBrushMinY) / p_relativeBrushSize;
                        float brushValue      = p_delta * p_alphaBrushTexture.GetPixelBilinear(u, v).a;
                        float directionFactor = p_targetHeight - height > 0 ? 1f : -1f;

                        if (Mathf.Sign(p_targetHeight - height) != Mathf.Sign(p_targetHeight - (height + directionFactor * brushValue)))
                        {
                            heights[indexX, indexY] = p_targetHeight;
                        }
                        else
                        {
                            heights[indexX, indexY] += directionFactor * brushValue;
                        }
                    }
                }
            }

            // inform listeners of the affected heights array with its values after the change
            if (OnAfterChangeHeights != null)
            {
                OnAfterChangeHeights(new HeightData(minY, minX, heights));
            }

            // apply the changed heights array
#if IS_DELAY_LOD_SUPPORTED
            m_terrainData.SetHeightsDelayLOD(minY, minX, heights);
#else
            m_terrainData.SetHeights(minY, minX, heights);
#endif

            // notify listeners that the level data was changed
            if (LE_EventInterface.OnChangeLevelData != null)
            {
                LE_EventInterface.OnChangeLevelData(this, new LE_LevelDataChangedEvent(LE_ELevelDataChangeType.TERRAIN_HEIGHTS));
            }
        }
Beispiel #5
0
 public void RemoveTerrainManager()
 {
     // notify listeners that the level data was changed
     if (m_terrain != null && LE_EventInterface.OnChangeLevelData != null)
     {
         LE_EventInterface.OnChangeLevelData(m_terrain.gameObject, new LE_LevelDataChangedEvent(LE_ELevelDataChangeType.TERRAIN_SELECTION));
     }
     m_terrain    = null;
     m_terrainMgr = null;
 }
Beispiel #6
0
 private void InitializeDefaultTerrain(LE_ConfigTerrain p_LEConfTerrain)
 {
     // initialize custom default terrain
     if (p_LEConfTerrain.CustomDefaultTerrain != null && p_LEConfTerrain.CustomDefaultTerrain.terrainData != null)
     {
         // save a reference to the default data prefab
         m_GUI3dTerrain.DefaultTerrainDataPrefab = p_LEConfTerrain.CustomDefaultTerrain.terrainData;
         // clone the terrain data so that the asset is not broken when testing in Unity Editor
         p_LEConfTerrain.CustomDefaultTerrain.enabled     = false;
         p_LEConfTerrain.CustomDefaultTerrain.terrainData = m_GUI3dTerrain.GetDefaultTerrainDataDeepCopy();
         if (p_LEConfTerrain.CustomDefaultTerrain.GetComponent <TerrainCollider>() != null)
         {
             p_LEConfTerrain.CustomDefaultTerrain.GetComponent <TerrainCollider>().terrainData = p_LEConfTerrain.CustomDefaultTerrain.terrainData;
         }
         else
         {
             Debug.LogError("LE_LevelEditorMain: the CustomDefaultTerrain assigned to LE_ConfigTerrain must have a collider!");
         }
         p_LEConfTerrain.CustomDefaultTerrain.Flush();
         p_LEConfTerrain.CustomDefaultTerrain.enabled = true;
         // access the custom predefined terrain data
         // and wrap it with a terrain manager, which is then assigned to the GUI3dTerrain instance
         m_GUI3dTerrain.SetTerrain(p_LEConfTerrain.CustomDefaultTerrain);
         // your terrain must be in the LE_ConfigTerrain.TerrainLayer layer, you can set this in the game object,
         // but it is included here so that you cannot forget it
         p_LEConfTerrain.CustomDefaultTerrain.gameObject.layer = p_LEConfTerrain.TerrainLayer;
         // just to be on the safe side call this event and notify listeners that the level data was changed
         if (LE_EventInterface.OnChangeLevelData != null)
         {
             LE_EventInterface.OnChangeLevelData(p_LEConfTerrain.CustomDefaultTerrain.gameObject, new LE_LevelDataChangedEvent(LE_ELevelDataChangeType.TERRAIN_LOADED_DEFAULT));
         }
         // a terrain does exist -> activate edit terrain UI
         if (LE_GUIInterface.Instance.delegates.SetTerrainUIMode != null)
         {
             LE_GUIInterface.Instance.delegates.SetTerrainUIMode(LE_GUIInterface.Delegates.ETerrainUIMode.EDIT);
         }
         else
         {
             Debug.LogWarning("LE_LevelEditorMain: you have not set the LE_GUIInterface.delegates.SetTerrainUIMode delegate. You need to set it for example if you want to disable the create UI if the default Unity terrain is set!");
         }
     }
     else
     {
         // there is no terrain -> activate create terrain UI
         if (LE_GUIInterface.Instance.delegates.SetTerrainUIMode != null)
         {
             LE_GUIInterface.Instance.delegates.SetTerrainUIMode(LE_GUIInterface.Delegates.ETerrainUIMode.CREATE);
         }
         else
         {
             Debug.LogWarning("LE_LevelEditorMain: you have not set the LE_GUIInterface.delegates.SetTerrainUIMode delegate. You need to set it for example if you want to show the create UI if there is no default Unity terrain set!");
         }
     }
 }
Beispiel #7
0
        public void SetTerrain(Terrain p_terrain)
        {
            if (m_terrainMgr != null)
            {
                Debug.LogError("LE_GUI3dTerrain: SetTerrain: a terrain manager was already set and will be overwritten! Use 'RemoveTerrainManager' to reset the instance.");
            }
            m_terrain    = p_terrain;
            m_terrainMgr = new LE_TerrainManager(p_terrain.terrainData);

            // notify listeners that the level data was changed
            if (LE_EventInterface.OnChangeLevelData != null)
            {
                LE_EventInterface.OnChangeLevelData(m_terrain.gameObject, new LE_LevelDataChangedEvent(LE_ELevelDataChangeType.TERRAIN_SELECTION));
            }
        }
Beispiel #8
0
 public void RemoveTerrainTexture(Texture2D p_texture)
 {
     if (m_confT.TerrainTextureConfig != null)
     {
         if (m_GUI3dTerrain != null && m_GUI3dTerrain.TerrainManager != null && m_GUI3dTerrain.TerrainManager.TerrainData != null)
         {
             SplatPrototype[] splatTexturesOld = m_GUI3dTerrain.TerrainManager.TerrainData.splatPrototypes;
             if (splatTexturesOld.Length > 0)
             {
                 bool             isTexFound       = false;
                 SplatPrototype[] splatTexturesNew = new SplatPrototype[splatTexturesOld.Length - 1];
                 for (int i = 0; i < splatTexturesOld.Length; i++)
                 {
                     if (!isTexFound && splatTexturesOld[i] != null && splatTexturesOld[i].texture == p_texture)
                     {
                         isTexFound = true;
                     }
                     else
                     {
                         splatTexturesNew[isTexFound ? i - 1 : i] = splatTexturesOld[i];
                     }
                 }
                 if (isTexFound)
                 {
                     m_GUI3dTerrain.TerrainManager.TerrainData.splatPrototypes = splatTexturesNew;
                     m_doRebuildTerrainTab = true;
                     // notify listeners that the level data was changed
                     if (LE_EventInterface.OnChangeLevelData != null)
                     {
                         LE_EventInterface.OnChangeLevelData(this, new LE_LevelDataChangedEvent(LE_ELevelDataChangeType.TERRAIN_TEXTURES));
                     }
                 }
                 else
                 {
                     Debug.LogError("LE_LogicTerrain: RemoveTerrainTexture: given texture is not a splat texture of the terrain!");
                 }
             }
             else
             {
                 Debug.LogError("LE_LogicTerrain: RemoveTerrainTexture: terrain has no splat textures set!");
             }
         }
     }
     else
     {
         Debug.LogError("LE_LogicTerrain: RemoveTerrainTexture: LE_ConfigTerrain has no TerrainTextureConfig set!");
     }
 }
Beispiel #9
0
 public static void SelectNewObjectAndNotifyListeners(LE_GUI3dObject p_gui3d, LE_Object p_newInstance)
 {
     // select new object
     p_gui3d.SelectObject(p_newInstance);
     // check if more objects of this kind can be placed
     p_gui3d.UpdateIsObjectPlaceable();
     // notify listeners that the level data was changed
     if (LE_EventInterface.OnChangeLevelData != null)
     {
         LE_EventInterface.OnChangeLevelData(p_newInstance, new LE_LevelDataChangedEvent(LE_ELevelDataChangeType.OBJECT_PLACE));
     }
     // notify listeners that an object has been placed
     if (LE_EventInterface.OnObjectPlaced != null)
     {
         LE_EventInterface.OnObjectPlaced(p_gui3d, new LE_ObjectPlacedEvent(p_newInstance));
     }
 }
Beispiel #10
0
 public static void DeleteObject(LE_GUI3dObject p_gui3d, LE_Object p_selectedObject)
 {
     if (p_selectedObject != null)
     {
         // if this object was snapped to any other object then reactivate the snap point to which this object was attached
         p_gui3d.ReactivateSnapPoints(p_selectedObject.UID, p_selectedObject.ObjectSnapPoints.Length);
         // destroy game object
         GameObject.Destroy(p_selectedObject.gameObject);
         // some script could search this kind of objects -> mark as deleted
         p_selectedObject.name = "deleted";
         // IsObjectPlaceable could have changed
         p_gui3d.UpdateIsObjectPlaceable();
         // notify listeners that the level data was changed
         if (LE_EventInterface.OnChangeLevelData != null)
         {
             LE_EventInterface.OnChangeLevelData(p_selectedObject.gameObject, new LE_LevelDataChangedEvent(LE_ELevelDataChangeType.OBJECT_DELETE));
         }
     }
 }
Beispiel #11
0
 private void OnSelectedObjectVariationIndexChanged(object p_obj, LE_GUIInterface.EventHandlers.IntEventArgs p_args)
 {
     if (m_GUI3dObject.SelectedObject != null)
     {
         bool isChanged = m_GUI3dObject.SelectedObject.VariationsDefaultIndex != p_args.Value;
         if (isChanged)
         {
             UR_CommandMgr.Instance.Execute(new LE_CmdChangeObjectVariation(m_GUI3dObject.SelectedObject, p_args.Value));
             // notify listeners that the level data was changed
             if (LE_EventInterface.OnChangeLevelData != null)
             {
                 LE_EventInterface.OnChangeLevelData(m_GUI3dObject.SelectedObject, new LE_LevelDataChangedEvent(LE_ELevelDataChangeType.OBJECT_VARIATION));
             }
         }
     }
     else
     {
         Debug.LogError("LE_LogicObject: OnSelectedObjectVariationIndexChanged was called, but the selected object does not allow to change this property (or nothing is selected)! You should listen to the LE_GUIInterface.delegates.SetSelectedObjectVariationPropertyValue and change the UI's state accordingly. This will prevent users from getting irritated by not working buttons.");
     }
 }
Beispiel #12
0
 private void OnSelectedObjectIsSleepOnStartChanged(object p_obj, LE_GUIInterface.EventHandlers.BoolEventArgs p_args)
 {
     if (m_GUI3dObject.SelectedObject != null && m_GUI3dObject.SelectedObject.IsRigidbodySleepingStartEditable)
     {
         bool isChanged = m_GUI3dObject.SelectedObject.IsRigidbodySleepingStart != p_args.Value;
         if (isChanged)
         {
             UR_CommandMgr.Instance.Execute(new LE_CmdChangeObjectIsSleepingStart(m_GUI3dObject.SelectedObject, p_args.Value));
             // notify listeners that the level data was changed
             if (LE_EventInterface.OnChangeLevelData != null)
             {
                 LE_EventInterface.OnChangeLevelData(m_GUI3dObject.SelectedObject, new LE_LevelDataChangedEvent(LE_ELevelDataChangeType.OBJECT_RIGIDBODY_SLEEPING_START));
             }
         }
     }
     else
     {
         Debug.LogError("LE_LogicObject: OnSelectedObjectIsSleepOnStartChanged was called, but the selected object does not allow to change this property (or nothing is selected)! You should listen to the LE_GUIInterface.delegates.SetIsSelectedObjectSleepPropertyInteractable and change the UI's state accordingly. This will prevent users from getting irritated by not working buttons.");
     }
 }
Beispiel #13
0
        public void PaintTexture(int p_splatPrototypeIndex, float p_delta, float p_targetValue, Texture2D p_alphaBrushTexture, float p_relativeBrushSize, Vector2 p_relativeLocalLocation)
        {
            if (p_splatPrototypeIndex < 0 || p_splatPrototypeIndex >= m_terrainData.splatPrototypes.Length)
            {
                Debug.LogError("LE_TerrainManager: PaintTexture: splat prototype index '" + p_splatPrototypeIndex + "' is out of bounds [0," + m_terrainData.splatPrototypes.Length + "]");
                return;
            }
            float[,,] alphaMaps;
            int   minX, maxX, minY, maxY;
            float relBrushMinX, relBrushMinY, alphamapMaxX, alphamapMaxY;

            alphamapMaxX = m_terrainData.alphamapWidth - 1;
            alphamapMaxY = m_terrainData.alphamapHeight - 1;
            GetAffectedAreaInternal(p_alphaBrushTexture, p_relativeBrushSize, p_relativeLocalLocation, alphamapMaxX, alphamapMaxY,
                                    out minX, out maxX, out minY, out maxY, out relBrushMinX, out relBrushMinY);
            // get the current alphamaps array
            alphaMaps = m_terrainData.GetAlphamaps(minY, minX, maxY - minY + 1, maxX - minX + 1);

            // inform listeners of the affected alphamaps array with its values before the change
            if (OnBeforeChangeAlphamaps != null)
            {
                OnBeforeChangeAlphamaps(new AlphamapData(minY, minX, alphaMaps));
            }

            // apply alpha change to every affected alpha map entry
            // according to given p_delta and the alpha value in p_alphaBrushTexture
            // alpha is always changed towards p_targetValue
            int   iterateToX = Mathf.Min(maxX - minX, alphaMaps.GetLength(0) - 1);
            int   iterateToY = Mathf.Min(maxY - minY, alphaMaps.GetLength(1) - 1);
            int   iterateToZ = alphaMaps.GetLength(2) - 1;
            float brushValue, alphaMapValue, signDiff;

            for (int indexX = 0; indexX <= iterateToX; indexX++)
            {
                for (int indexY = 0; indexY <= iterateToY; indexY++)
                {
                    alphaMapValue = alphaMaps[indexX, indexY, p_splatPrototypeIndex];
                    if (Mathf.Abs(alphaMapValue - p_targetValue) > 0.0001f)
                    {
                        signDiff   = Mathf.Sign(p_targetValue - alphaMapValue);
                        brushValue = p_delta * signDiff * p_alphaBrushTexture.GetPixelBilinear(
                            (((float)(indexY + minY) / alphamapMaxY) - relBrushMinY) / p_relativeBrushSize,
                            (((float)(indexX + minX) / alphamapMaxX) - relBrushMinX) / p_relativeBrushSize).a;

                        // apply change in the selected layer
                        if (signDiff != Mathf.Sign(p_targetValue - (alphaMapValue + brushValue)))
                        {
                            alphaMapValue = p_targetValue;
                        }
                        else
                        {
                            alphaMapValue += brushValue;
                        }
                        alphaMaps[indexX, indexY, p_splatPrototypeIndex] = alphaMapValue;
                        // normilize the other layers
                        float alphaMapsSum = 0;
                        // calculate the sum of the other layers
                        for (int indexZ = 0; indexZ <= iterateToZ; indexZ++)
                        {
                            if (indexZ != p_splatPrototypeIndex)
                            {
                                alphaMapsSum += alphaMaps[indexX, indexY, indexZ];
                            }
                        }
                        // if the other layers have values, then reduce those to get a normalized result
                        if (alphaMapsSum != 0)
                        {
                            float normalizer = (1f - alphaMapValue) / alphaMapsSum;
                            for (int indexZ = 0; indexZ <= iterateToZ; indexZ++)
                            {
                                if (indexZ != p_splatPrototypeIndex)
                                {
                                    alphaMaps[indexX, indexY, indexZ] *= normalizer;
                                }
                            }
                        }
                        // if the other layers have no values, but the target layers is not normalized, then...
                        else if (alphaMapValue != 1)
                        {
                            if (p_splatPrototypeIndex != 0)
                            {
                                // fill up the base layer with the missing amount to normalization
                                alphaMaps[indexX, indexY, 0] = 1f - alphaMapValue;
                            }
                            else
                            {
                                // and do not allow to decrease the base layer opacity
                                alphaMaps[indexX, indexY, 0] = 1f;
                            }
                        }
                    }
                }
            }

            // apply the changed alpha maps
            m_terrainData.SetAlphamaps(minY, minX, alphaMaps);

            // inform listeners of the affected alphamaps array with its values after the change
            if (OnAfterChangeAlphamaps != null)
            {
                OnAfterChangeAlphamaps(new AlphamapData(minY, minX, alphaMaps));
            }

            // notify listeners that the level data was changed
            if (LE_EventInterface.OnChangeLevelData != null)
            {
                LE_EventInterface.OnChangeLevelData(this, new LE_LevelDataChangedEvent(LE_ELevelDataChangeType.TERRAIN_ALPHAMAPS));
            }
        }
Beispiel #14
0
        public void SmoothHeight(float p_amount, int p_neighbourCount, Texture2D p_alphaBrushTexture, float p_relativeBrushSize, Vector2 p_relativeLocalLocation, bool p_isDirected, float p_angle)
        {
            float[,] heights;
            int   minX, maxX, minY, maxY;
            float relBrushMinX, relBrushMinY, heightmapMaxX, heightmapMaxY;

            heightmapMaxX = m_terrainData.heightmapWidth - 1;
            heightmapMaxY = m_terrainData.heightmapHeight - 1;
            // the affected area is bigger than the brush size, because of the neighbourcount
            float oversizedRelativeBrushSize = Mathf.Clamp01(p_relativeBrushSize + (float)(p_neighbourCount - 1) / Mathf.Max(heightmapMaxX, heightmapMaxY));

            GetAffectedAreaInternal(p_alphaBrushTexture, oversizedRelativeBrushSize, p_relativeLocalLocation, heightmapMaxX, heightmapMaxY,
                                    out minX, out maxX, out minY, out maxY, out relBrushMinX, out relBrushMinY);
            // to calculate which part of the oversized affected area is really smoothed (there are borders that a read, but not smoothed)
            // we need to calculate the affected area of the brush
            int   minXWrite, maxXWrite, minYWrite, maxYWrite;
            float relBrushMinXWrite, relBrushMinYWrite;

            GetAffectedAreaInternal(p_alphaBrushTexture, p_relativeBrushSize, p_relativeLocalLocation, heightmapMaxX, heightmapMaxY,
                                    out minXWrite, out maxXWrite, out minYWrite, out maxYWrite, out relBrushMinXWrite, out relBrushMinYWrite);
            // get the current read and write heights array
            heights = m_terrainData.GetHeights(minY, minX, maxY - minY + 1, maxX - minX + 1);

            // inform listeners of the affected heights array with its values before the change
            if (OnBeforeChangeHeights != null)
            {
                OnBeforeChangeHeights(new HeightData(minY, minX, heights));
            }

            // smoothing code inspired by Sándor Moldán's Unity Terrain Toolkit (Unity Summer of Code 2009)
            int iterateFromX      = minXWrite - minX;
            int iterateFromY      = minYWrite - minY;
            int iterateToXReadMax = heights.GetLength(0) - 1;
            int iterateToYReadMax = heights.GetLength(1) - 1;
            int iterateToX        = Mathf.Min(iterateFromX + maxXWrite - minXWrite, iterateToXReadMax);
            int iterateToY        = Mathf.Min(iterateFromY + maxYWrite - minYWrite, iterateToYReadMax);
            // smooth
            int   neighbourCountHalf = (p_neighbourCount - 1) / 2;
            int   xNeighbours, yNeighbours, xShift, yShift, Tx, Ty;
            float u, v, brushValue, oldValue;

            for (Ty = iterateFromY; Ty <= iterateToY; Ty++)
            {
                // get number of neighbours on Y
                if (Ty == 0)                 // Ty is on left edge of array -> go in one direction only
                {
                    yNeighbours = neighbourCountHalf + 1;
                    yShift      = 0;
                }
                else if (Ty == iterateToYReadMax)                 // Ty is on right edge of array -> go in one direction only
                {
                    yNeighbours = neighbourCountHalf + 1;
                    yShift      = -neighbourCountHalf;
                }
                else if (Ty - neighbourCountHalf < 0)                 // Ty is too close to left edge of array -> limit # of look ups in the left direction
                {
                    int outRange = (neighbourCountHalf - Ty);
                    yNeighbours = p_neighbourCount - outRange;
                    yShift      = -neighbourCountHalf + outRange;
                }
                else if (Ty + neighbourCountHalf >= iterateToYReadMax)                 // Ty is too close to right edge of array -> limit # of look ups in the right direction
                {
                    int outRange = (neighbourCountHalf + Ty) - iterateToYReadMax;
                    yNeighbours = p_neighbourCount - outRange;
                    yShift      = -neighbourCountHalf;
                }
                else                 // Ty is in the middle of array -> look as much as possible
                {
                    yNeighbours = p_neighbourCount;
                    yShift      = -neighbourCountHalf;
                }
                for (Tx = iterateFromX; Tx <= iterateToX; Tx++)
                {
                    // get number of neighbours on X
                    if (Tx == 0)                     // Tx is on left edge of array -> go in one direction only
                    {
                        xNeighbours = neighbourCountHalf + 1;
                        xShift      = 0;
                    }
                    else if (Tx == iterateToXReadMax)                     // Tx is on right edge of array -> go in one direction only
                    {
                        xNeighbours = neighbourCountHalf + 1;
                        xShift      = -neighbourCountHalf;
                    }
                    else if (Tx - neighbourCountHalf < 0)                     // Tx is too close to left edge of array -> limit # of look ups in the left direction
                    {
                        int outRange = (neighbourCountHalf - Tx);
                        xNeighbours = p_neighbourCount - outRange;
                        xShift      = -neighbourCountHalf + outRange;
                    }
                    else if (Tx + neighbourCountHalf >= iterateToXReadMax)                     // Tx is too close to right edge of array -> limit # of look ups in the right direction
                    {
                        int outRange = (neighbourCountHalf + Tx) - iterateToXReadMax;
                        xNeighbours = p_neighbourCount - outRange;
                        xShift      = -neighbourCountHalf;
                    }
                    else                     // Tx is in the middle of array -> look as much as possible
                    {
                        xNeighbours = p_neighbourCount;
                        xShift      = -neighbourCountHalf;
                    }
                    // smooth
                    int   Ny, Nx;
                    float hCumulative = 0.0f;
                    int   nNeighbours = 0;
                    // calculate the sum of all heights in the neighbourhood
                    for (Ny = 0; Ny < yNeighbours; Ny++)
                    {
                        for (Nx = 0; Nx < xNeighbours; Nx++)
                        {
                            if (p_isDirected)
                            {
                                int neighbourOffsetX = Nx + xShift;
                                int neighbourOffsetY = Ny + yShift;
                                if (neighbourOffsetX != 0 || neighbourOffsetY != 0)
                                {
                                    Vector2 dir = new Vector2(neighbourOffsetX, neighbourOffsetY).normalized;
                                    float   angle;
                                    if (dir.y >= 0)
                                    {
                                        angle = Mathf.Rad2Deg * Mathf.Acos(dir.x);
                                    }
                                    else
                                    {
                                        angle = Mathf.Rad2Deg * Mathf.Asin(dir.y);
                                        if (dir.x < 0)
                                        {
                                            angle = -90 - (90 + angle);
                                        }
                                    }
                                    if (Mathf.Abs(Mathf.DeltaAngle(p_angle, angle)) > 5f &&
                                        Mathf.Abs(Mathf.DeltaAngle(p_angle, angle + 180)) > 5f)
                                    {
                                        continue;
                                    }
                                }
                            }
                            float heightAtPoint = heights[Tx + Nx + xShift, Ty + Ny + yShift];
                            hCumulative += heightAtPoint;
                            nNeighbours++;
                        }
                    }
                    float hAverage = hCumulative / nNeighbours;
                    // apply smoothed result
                    oldValue        = heights[Tx, Ty];
                    v               = (((float)(Tx + minX) / heightmapMaxX) - relBrushMinXWrite) / p_relativeBrushSize;
                    u               = (((float)(Ty + minY) / heightmapMaxY) - relBrushMinYWrite) / p_relativeBrushSize;
                    brushValue      = p_amount * p_alphaBrushTexture.GetPixelBilinear(u, v).a;
                    heights[Tx, Ty] = oldValue * (1f - brushValue) + hAverage * brushValue;
                }
            }

            // apply the changed heights array
#if IS_DELAY_LOD_SUPPORTED
            m_terrainData.SetHeightsDelayLOD(minY, minX, heights);
#else
            m_terrainData.SetHeights(minY, minX, heights);
#endif

            // inform listeners of the affected heights array with its values after the change
            if (OnAfterChangeHeights != null)
            {
                OnAfterChangeHeights(new HeightData(minY, minX, heights));
            }

            // notify listeners that the level data was changed
            if (LE_EventInterface.OnChangeLevelData != null)
            {
                LE_EventInterface.OnChangeLevelData(this, new LE_LevelDataChangedEvent(LE_ELevelDataChangeType.TERRAIN_HEIGHTS));
            }
        }