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); }
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!"); } }
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)); } }
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; }
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!"); } } }
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)); } }
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!"); } }
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)); } }
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)); } } }
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."); } }
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."); } }
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)); } }
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)); } }