Modifying terrain height under a gameobject at runtime

I've checked this and the Terrain and TerrainData reference.

But I tried and cannot figure out how to modify the terrain by scripting at runtime. What I actually want is to be able to raise the terrain under a certain gameobject.

Any help or examples will be appreaciated. Thanks

thank for yor answer Duck, I finally figured it out. I paste the code I used so that anyone who has the same problem can check it out. It is not that good but I hope it helps.

Unluckily if you modify the terrain at runtime it doesnt reset to the original heights it had before you played the scene.

Also, I havent thought of an algorithm to raise the terrain in a smooth way (this is plain blocky), but it would be good to raise it smoothly as if a mountain was raising.

Terrain terr; // terrain to modify
int hmWidth; // heightmap width
int hmHeight; // heightmap height

int posXInTerrain; // position of the game object in terrain width (x axis)
int posYInTerrain; // position of the game object in terrain height (z axis)

int size = 50; // the diameter of terrain portion that will raise under the game object
float desiredHeight = 0; // the height we want that portion of terrain to be

void Start () {

	terr = Terrain.activeTerrain;
	hmWidth = terr.terrainData.heightmapWidth;
	hmHeight = terr.terrainData.heightmapHeight;

}

void Update () {

	// get the normalized position of this game object relative to the terrain
	Vector3 tempCoord = (transform.position - terr.gameObject.transform.position);
	Vector3 coord;
	coord.x = tempCoord.x / terr.terrainData.size.x;
	coord.y = tempCoord.y / terr.terrainData.size.y;
	coord.z = tempCoord.z / terr.terrainData.size.z;

	// get the position of the terrain heightmap where this game object is
	posXInTerrain = (int) (coord.x * hmWidth); 
	posYInTerrain = (int) (coord.z * hmHeight);

	// we set an offset so that all the raising terrain is under this game object
	int offset = size / 2;

	// get the heights of the terrain under this game object
	float[,] heights = terr.terrainData.GetHeights(posXInTerrain-offset,posYInTerrain-offset,size,size);

	// we set each sample of the terrain in the size to the desired height
	for (int i=0; i < size; i++)
		for (int j=0; j < size; j++)
			heights[i,j] = desiredHeight;

	// go raising the terrain slowly
	desiredHeight += Time.deltaTime;

	// set the new height
	terr.terrainData.SetHeights(posXInTerrain-offset,posYInTerrain-offset,heights);

}

You need to use GetHeights and SetHeights. Specifically:

  • convert your GameObject's position to normalized terrain coordinates (i.e. in the range 0-1)
  • use terrainData.GetHeights(x,y,width,height) to get an array containing the current height at that location (in your case, width and height would probably be 1,1)
  • Modify the values in the array, to increase the height
  • use terrainData.SetHeights(x,y,yourHeightArray) to write the new heights back to that location on the terrain.

I made a little modification for code. Took couple of hours, so there maid be a fine adjusting…

This is, what I’m using, when settling buildings over terrain, which is not flat.

Before inserting a building to the terrain, first look terrain height with raycast at wanted position.

Then call the routine with raycasted height + xxx (extra height). You can set width & height & terrain texture with size also.

Routine can fine adjust raised terrain edges within given distance from newly levelled area.

If you want to test this piece of fixed code, just create a new scene. Add terrain to it. Add two textures to it. Add camera pointing to terrain. Add this script to your terrain inspector. Left mouse click raises and right lovers pointed terrain. Also, enable “Test with mouse” from scipt’s inspector.

using UnityEngine;
using System.Collections;

public class RaiseLowerTerrain : MonoBehaviour
{
	public bool TestWithMouse = false;
	public Terrain myTerrain;
	public int SmoothArea;
	private int xResolution;
	private int zResolution;
	private float[,] heights;
	private float[,] heightMapBackup;
	protected const float DEPTH_METER_CONVERT=0.05f;
	protected const float TEXTURE_SIZE_MULTIPLIER = 1.25f;
	public int DeformationTextureNum = 1;
	protected int alphaMapWidth;
	protected int alphaMapHeight;
	protected int numOfAlphaLayers;
	private float[, ,] alphaMapBackup;


	void Start()
	{
		xResolution = myTerrain.terrainData.heightmapWidth;
		zResolution = myTerrain.terrainData.heightmapHeight;
		alphaMapWidth = myTerrain.terrainData.alphamapWidth;
		alphaMapHeight = myTerrain.terrainData.alphamapHeight;
		numOfAlphaLayers = myTerrain.terrainData.alphamapLayers;

		if (Debug.isDebugBuild)
		{
			heights = myTerrain.terrainData.GetHeights (0, 0, xResolution, zResolution);	
			heightMapBackup = myTerrain.terrainData.GetHeights(0, 0, xResolution, zResolution);
			alphaMapBackup = myTerrain.terrainData.GetAlphamaps(0, 0, alphaMapWidth, alphaMapHeight);   
	    }

	}

	void OnApplicationQuit()
	{
		if (Debug.isDebugBuild)
		{
			myTerrain.terrainData.SetHeights(0, 0, heightMapBackup);
			myTerrain.terrainData.SetAlphamaps(0, 0, alphaMapBackup);
		}
	}

	
	void Update()
	{
		// This is just for testing with mouse!
		// Point mouse to the Terrain. Left mouse button
		// raises and right mouse button lowers terrain.
		if (TestWithMouse == true) 
		{
			if (Input.GetMouseButtonDown (0)) 
			{
				RaycastHit hit;
				Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
				if (Physics.Raycast (ray, out hit)) 
				{
					// area middle point x and z, area width, area height, smoothing distance, area height adjust
					raiselowerTerrainArea (hit.point, 10, 10, SmoothArea, 0.01f); 
					// area middle point x and z, area size, texture ID from terrain textures
					TextureDeformation (hit.point, 10 * 2f, DeformationTextureNum);
				}
			}
			if (Input.GetMouseButtonDown (1)) 
			{
				RaycastHit hit;
				Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
				if (Physics.Raycast (ray, out hit)) 
				{
					// area middle point x and z, area width, area height, smoothing distance, area height adjust
					raiselowerTerrainArea (hit.point, 10, 10, SmoothArea, -0.01f);
					// area middle point x and z, area size, texture ID from terrain textures
					TextureDeformation (hit.point, 10 * 2f, 0);
				}
			}
		}
	}
	

	private void raiselowerTerrainArea(Vector3 point, int lenx, int lenz, int smooth, float incdec)
	{
		int areax;
		int areaz;
		smooth += 1;
		float smoothing;
		int terX =(int)((point.x / myTerrain.terrainData.size.x) * xResolution);
		int terZ =(int)((point.z / myTerrain.terrainData.size.z) * zResolution);
		lenx += smooth;
		lenz += smooth;
		terX -= (lenx / 2);
		terZ -= (lenz / 2);
		if (terX < 0) terX = 0;
		if (terX > xResolution)	terX = xResolution;
		if (terZ < 0) terZ = 0;
		if (terZ > zResolution)	terZ = zResolution;
		float[,] heights = myTerrain.terrainData.GetHeights(terX, terZ, lenx, lenz);
		float y = heights[lenx/2,lenz/2];
		y += incdec;
		for (smoothing=1; smoothing < smooth+1; smoothing++) 
		{
			float multiplier = smoothing / smooth;
			for (areax = (int)(smoothing/2); areax < lenx-(smoothing/2); areax++) 
			{
				for (areaz = (int)(smoothing/2); areaz < lenz-(smoothing/2); areaz++) 
				{
					if ((areax > -1) && (areaz > -1) && (areax < xResolution) && (areaz < zResolution)) 
					{
						heights [areax, areaz] = Mathf.Clamp((float)y*multiplier,0,1);
					}
				}
			}
		}
		myTerrain.terrainData.SetHeights (terX, terZ, heights);
	}

	private void raiselowerTerrainPoint(Vector3 point, float incdec)
	{
		int terX =(int)((point.x / myTerrain.terrainData.size.x) * xResolution);
		int terZ =(int)((point.z / myTerrain.terrainData.size.z) * zResolution);
		float y = heights[terX,terZ];
		y += incdec;
		float[,] height = new float[1,1];
		height[0,0] = Mathf.Clamp(y,0,1);
		heights[terX,terZ] = Mathf.Clamp(y,0,1);
		myTerrain.terrainData.SetHeights(terX, terZ, height);
	}

	protected void TextureDeformation(Vector3 pos, float craterSizeInMeters,int textureIDnum)
	{
		Vector3 alphaMapTerrainPos = GetRelativeTerrainPositionFromPos(pos, myTerrain, alphaMapWidth, alphaMapHeight);
		int alphaMapCraterWidth = (int)(craterSizeInMeters * (alphaMapWidth / myTerrain.terrainData.size.x));
		int alphaMapCraterLength = (int)(craterSizeInMeters * (alphaMapHeight / myTerrain.terrainData.size.z));
		int alphaMapStartPosX = (int)(alphaMapTerrainPos.x - (alphaMapCraterWidth / 2));
		int alphaMapStartPosZ = (int)(alphaMapTerrainPos.z - (alphaMapCraterLength/2));
		float[, ,] alphas = myTerrain.terrainData.GetAlphamaps(alphaMapStartPosX, alphaMapStartPosZ, alphaMapCraterWidth, alphaMapCraterLength);
		float circlePosX;
		float circlePosY;
		float distanceFromCenter;
		for (int i = 0; i < alphaMapCraterLength; i++) //width
		{
			for (int j = 0; j < alphaMapCraterWidth; j++) //height
			{
				circlePosX = (j - (alphaMapCraterWidth / 2)) / (alphaMapWidth / myTerrain.terrainData.size.x);
				circlePosY = (i - (alphaMapCraterLength / 2)) / (alphaMapHeight / myTerrain.terrainData.size.z);
				distanceFromCenter = Mathf.Abs(Mathf.Sqrt(circlePosX * circlePosX + circlePosY * circlePosY));
				if (distanceFromCenter < (craterSizeInMeters / 2.0f))
				{
					for (int layerCount = 0; layerCount < numOfAlphaLayers; layerCount++)
					{
						//could add blending here in the future
						if (layerCount == textureIDnum)
						{
							alphas[i, j, layerCount] = 1;
						}
						else
						{
							alphas[i, j, layerCount] = 0;
						}
					}
				}
			}
		} 
		myTerrain.terrainData.SetAlphamaps(alphaMapStartPosX, alphaMapStartPosZ, alphas);
	}

	protected Vector3 GetNormalizedPositionRelativeToTerrain(Vector3 pos, Terrain terrain)
	{
		Vector3 tempCoord = (pos - terrain.gameObject.transform.position);
		Vector3 coord;
		coord.x = tempCoord.x / myTerrain.terrainData.size.x;
		coord.y = tempCoord.y / myTerrain.terrainData.size.y;
		coord.z = tempCoord.z / myTerrain.terrainData.size.z;
		return coord;
	}

	protected Vector3 GetRelativeTerrainPositionFromPos(Vector3 pos,Terrain terrain, int mapWidth, int mapHeight)
	{
		Vector3 coord = GetNormalizedPositionRelativeToTerrain(pos, terrain);
		return new Vector3((coord.x * mapWidth), 0, (coord.z * mapHeight));
	}     
}

rgds:
Jari Kaija
jari.kaija@live.com

using UnityEngine;

/// <summary>
/// Raise or lower terrain.
/// </summary>
public class RaiseLowerTerrain:MonoBehaviour {

    [SerializeField]
    public Terrain myTerrain;

    [Header("Digging Size")]

    [SerializeField]
    public int SmoothArea = 2;

    [SerializeField]
    public float DirtAmount = -5f;

    [SerializeField]
    public int HoleSize = 2;

    [Header("Retexture Ground")]

    [SerializeField]
    public bool ChangeTexture = true;

    [SerializeField]
    /// <summary>The texture ID to change texture to</summary>
    public int ChangeTextureTo = 3;


    private int xResolution;
    private int zResolution;
    private float[,] heights;
    private float[,] heightMapBackup;
    protected const float DEPTH_METER_CONVERT = 0.05f;
    protected const float TEXTURE_SIZE_MULTIPLIER = 1.25f;
    protected int alphaMapWidth;
    protected int alphaMapHeight;
    protected int numOfAlphaLayers;
    private float[,,] alphaMapBackup;



    void Start() {
        xResolution = myTerrain.terrainData.heightmapWidth;
        zResolution = myTerrain.terrainData.heightmapHeight;
        alphaMapWidth = myTerrain.terrainData.alphamapWidth;
        alphaMapHeight = myTerrain.terrainData.alphamapHeight;
        numOfAlphaLayers = myTerrain.terrainData.alphamapLayers;
    }



    /// <summary>
    /// Used for testing - Point mouse to the Terrain. Left mouse button to raise/Right to lower
    /// NOTE: Comment this out once testing is over
    /// </summary>
    void Update() {
        if (Input.GetMouseButtonDown(0)) {
            TestMoveDirt(true);
        }
        if (Input.GetMouseButtonDown(1)) {
            TestMoveDirt(false);
        }
    }

    /// <summary>
    /// Lower or Raise dirt based on where player is looking
    /// </summary>
    /// <param name="raise">If set to <c>true</c> raise.</param>
    private void TestMoveDirt(bool raise) {
        float dirtAmount = raise ? DirtAmount : -1 * DirtAmount;

        RaycastHit hit;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        if (Physics.Raycast(ray, out hit)) {

            // area middle point x and z, area width, area height, smoothing distance, area height adjust
            raiselowerTerrainArea(hit.point, HoleSize, HoleSize, SmoothArea, dirtAmount);

            // area middle point x and z, area size, texture ID from terrain textures
            if (ChangeTexture)
                TextureDeformation(hit.point, HoleSize * HoleSize, ChangeTextureTo);

        }
    }




    /// <summary>
    /// The Main function to call - Lower or Raise height of terrain at postion by "DirtAmount" in a hole the size of "HoleSize"
    /// Note: You will likely have to pass in your own Raycast data in which form you need
    /// </summary>
    /// <param name="hit">Raycast data.</param>
    public void MoveDirt(RaycastHit hit) {
        raiselowerTerrainArea(hit.point, HoleSize, HoleSize, SmoothArea, DirtAmount);

        if (ChangeTexture)
            TextureDeformation(hit.point, HoleSize * HoleSize, ChangeTextureTo);
    }




    /// <summary>
    /// Raise or lower 1 section of terrain by an amount, but will be jagged and not smoothed
    /// </summary>
    /// <param name="point">Point to change height at x/z</param>
    /// <param name="incdec">Amount to raise or lower (1 = small, 100 = large)</param>
    private void raiselowerTerrainChunk(Vector3 point, float incdec) {
        incdec *= 0.00001f; //NOTE: These are in units of 0-1 so need to be very small at FPS level

        int terX = (int)((point.x / myTerrain.terrainData.size.x) * xResolution);
        int terZ = (int)((point.z / myTerrain.terrainData.size.z) * zResolution);

        if (terX < 0) terX = 0;
        if (terX > xResolution) terX = xResolution;
        if (terZ < 0) terZ = 0;
        if (terZ > zResolution) terZ = zResolution;

        float[,] heights = myTerrain.terrainData.GetHeights(terX, terZ, 1, 1);
        float y = heights[0, 0];

        heights[0, 0] += incdec;

        myTerrain.terrainData.SetHeights(terX, terZ, heights);

    }

    /// <summary>
    /// Raise or lowers the terrain in an area and applies smoothing.
    /// </summary>
    /// <param name="point">Point to change height at x/z</param>
    /// <param name="lenx">X Width of tiles to modify height</param>
    /// <param name="lenz">Z Width of tiles to modify height</param>
    /// <param name="smooth">Number of tiles radius to smooth</param>
    /// <param name="incdec">Amount to raise or lower (1 = small, 100 = large)</param>
    private void raiselowerTerrainArea(Vector3 point, int lenx, int lenz, int smooth, float incdec) {
        //From http://answers.unity3d.com/questions/420634/how-do-you-dynamically-alter-terrain.html, modified by Jay

        incdec *= 0.00001f; //NOTE: These are in units of 0-1 so need to be very small at FPS level

        int areax;
        int areaz;
        smooth += 1;

        float smoothing;
        int terX = (int)((point.x / myTerrain.terrainData.size.x) * xResolution);
        int terZ = (int)((point.z / myTerrain.terrainData.size.z) * zResolution);
        lenx += smooth;
        lenz += smooth;
        terX -= (lenx / 2);
        terZ -= (lenz / 2);
        if (terX < 0) terX = 0;
        if (terX > xResolution) terX = xResolution;
        if (terZ < 0) terZ = 0;
        if (terZ > zResolution) terZ = zResolution;

        float[,] heights = myTerrain.terrainData.GetHeights(terX, terZ, lenx, lenz);
        float y = heights[lenx / 2, lenz / 2];
        y += incdec;

        for (smoothing = 1; smoothing < (smooth + 1); smoothing++) {
            float multiplier = smoothing / smooth;
            for (areax = (int)(smoothing / 2); areax < lenx - (smoothing / 2); areax++) {
                for (areaz = (int)(smoothing / 2); areaz < lenz - (smoothing / 2); areaz++) {
                    if ((areax > -1) && (areaz > -1) && (areax < xResolution) && (areaz < zResolution)) {
                        heights[areax, areaz] += (float)(incdec * multiplier);
                        heights[areax, areaz] = Mathf.Clamp(heights[areax, areaz], 0, 1);
                    }
                }
            }
        }
        myTerrain.terrainData.SetHeights(terX, terZ, heights);
    }

    private void raiselowerTerrainPoint(Vector3 point, float incdec) {
        int terX = (int)((point.x / myTerrain.terrainData.size.x) * xResolution);
        int terZ = (int)((point.z / myTerrain.terrainData.size.z) * zResolution);
        float y = heights[terX, terZ];
        y += incdec;
        float[,] height = new float[1, 1];
        height[0, 0] = Mathf.Clamp(y, 0, 1);
        heights[terX, terZ] = Mathf.Clamp(y, 0, 1);
        myTerrain.terrainData.SetHeights(terX, terZ, height);
    }

    /// <summary>
    /// Blend Textures into Terrain
    /// </summary>
    /// <param name="pos">Position.</param>
    /// <param name="craterSizeInMeters">Area size in meters.</param>
    /// <param name="textureIDnum">Texture identifier number (from Terrain textures).</param>
    protected void TextureDeformation(Vector3 pos, float craterSizeInMeters, int textureIDnum) {
        Vector3 alphaMapTerrainPos = GetRelativeTerrainPositionFromPos(pos, myTerrain, alphaMapWidth, alphaMapHeight);
        int alphaMapCraterWidth = (int)(craterSizeInMeters * (alphaMapWidth / myTerrain.terrainData.size.x));
        int alphaMapCraterLength = (int)(craterSizeInMeters * (alphaMapHeight / myTerrain.terrainData.size.z));
        int alphaMapStartPosX = (int)(alphaMapTerrainPos.x - (alphaMapCraterWidth / 2));
        int alphaMapStartPosZ = (int)(alphaMapTerrainPos.z - (alphaMapCraterLength / 2));
        float[,,] alphas = myTerrain.terrainData.GetAlphamaps(alphaMapStartPosX, alphaMapStartPosZ, alphaMapCraterWidth, alphaMapCraterLength);
        float circlePosX;
        float circlePosY;
        float distanceFromCenter;
        for (int i = 0; i < alphaMapCraterLength; i++) //width
        {
            for (int j = 0; j < alphaMapCraterWidth; j++) //height
            {
                circlePosX = (j - (alphaMapCraterWidth / 2)) / (alphaMapWidth / myTerrain.terrainData.size.x);
                circlePosY = (i - (alphaMapCraterLength / 2)) / (alphaMapHeight / myTerrain.terrainData.size.z);
                distanceFromCenter = Mathf.Abs(Mathf.Sqrt(circlePosX * circlePosX + circlePosY * circlePosY));
                if (distanceFromCenter < (craterSizeInMeters / 2.0f)) {
                    for (int layerCount = 0; layerCount < numOfAlphaLayers; layerCount++) {
                        //could add blending here in the future
                        if (layerCount == textureIDnum) {
                            alphas[i, j, layerCount] = 1;
                        } else {
                            alphas[i, j, layerCount] = 0;
                        }
                    }
                }
            }
        }
        myTerrain.terrainData.SetAlphamaps(alphaMapStartPosX, alphaMapStartPosZ, alphas);
    }

    protected Vector3 GetNormalizedPositionRelativeToTerrain(Vector3 pos, Terrain terrain) {
        Vector3 tempCoord = (pos - terrain.gameObject.transform.position);
        Vector3 coord;
        coord.x = tempCoord.x / myTerrain.terrainData.size.x;
        coord.y = tempCoord.y / myTerrain.terrainData.size.y;
        coord.z = tempCoord.z / myTerrain.terrainData.size.z;
        return coord;
    }

    protected Vector3 GetRelativeTerrainPositionFromPos(Vector3 pos, Terrain terrain, int mapWidth, int mapHeight) {
        Vector3 coord = GetNormalizedPositionRelativeToTerrain(pos, terrain);
        return new Vector3((coord.x * mapWidth), 0, (coord.z * mapHeight));
    }
}

Terrain Destruction does all this for you. It’s cheap and simple, and fast.

That script above worked great.
Could you tell me what part of the script I should be looking at to say deform the terrain less.
And how would go attaching the script to a object like a shovel or a pick axe so the players could flatten at a slow rate.
Script works great :slight_smile:
But It would be nice to punch a hole in terrain and stitch it internally to make a cave.

I’ve resurrected this thread as I have searched for terrain modification over… twenty times, and I wanted to share this with anyone attempting to understand it like I have struggled to over the past two (ish) years.

Here is the type of code from Unity3d 2019.2.1f1 that I have finally understood, and it works (kinda).

Granted I cannot take all the credit but this has been me putting other known stuff out there together to come up with a kind of solution. This is in C#, Gameobject has rigidbody, and a box collider trigger:

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

public class Object_Terrain_Modifiy : MonoBehaviour
{
    public Terrain terrain; //choose our terrain

   // set the strength low as everything in the terrain map is between [0,1]
    public float strength = 0.001f; 

 //get raycast down
    public float Ray_distance = 2f;

    private int heightmapWidth; //terrain dimensions
    private int heightmapHeight; //terrain dimensions
    private float[,] heights; //still don't quite truly understand this guy
    private TerrainData terrainData; //all the fun stuff the Unity terrain provides


    // Start is called before the first frame update
    void Start()
    {
        terrainData = terrain.terrainData;

        heightmapHeight = terrainData.heightmapHeight;
        heightmapWidth = terrainData.heightmapWidth;
        heights = terrainData.GetHeights(0, 0, heightmapWidth, heightmapHeight);
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        RaycastHit hit;
        
        
            if (Physics.Raycast(this.gameObject.transform.position, -Vector3.up, out hit, Ray_distance) || Physics.Raycast(this.gameObject.transform.position, Vector3.up, out hit))
            {
            if (hit.point.y < (this.gameObject.transform.position.y - (this.gameObject.transform.localScale.y / 2)) && hit.collider.tag == "Terrain")
            {
                //if the terrain has been caught beneath the object do this function
                RaiseTerrain(hit.point);
            }
            

            if (hit.point.y > (this.gameObject.transform.position.y - (this.gameObject.transform.localScale.y / 2)) + 0.5f && hit.collider.tag == "Terrain")
            {
                //if the terrain is above the object or on the side of the object flatten terrain
                LowerTerrain(hit.point);
            }

            if (!(hit.point.y < (this.gameObject.transform.position.y - (this.gameObject.transform.localScale.y / 2))) && (!(hit.point.y > (this.gameObject.transform.position.y - (this.gameObject.transform.localScale.y / 2)) + 0.5f)) && hit.collider.tag == "Terrain")
            {
                this.gameObject.GetComponent<Object_Terrain_Modifiy>().enabled = false;
            }

        }

        Debug.DrawRay(this.gameObject.transform.position, -Vector3.up, Color.red, Ray_distance);
    }

    private void RaiseTerrain(Vector3 point)
    {
        //get the location of where the object is and where we calculate the object related to the position  //on the terrain
        int mouseX = (int)((point.x / terrainData.size.x) * heightmapWidth);
        int mouseZ = (int)((point.z / terrainData.size.z) * heightmapHeight);

        Debug.Log("what is X: " + mouseX);
        Debug.Log("what is Z: " + mouseZ);

        float[,] modifiedHeights = new float[1, 1];
        float y = heights[mouseX, mouseZ];

        
        //increase terrain
            y += strength * Time.deltaTime;
        

        
//if terrain Y pos is maxed spot creating higher terrain
        if (y > terrainData.size.y)
        {
            y = terrainData.size.y;
        }

        modifiedHeights[0, 0] = y;

        
//get a square offset around the object we want the terrain to support land under
        for (int i = -(int)this.gameObject.transform.localScale.x; i < (this.gameObject.transform.localScale.x * 2); i++)
        {
            for (int z = -(int)this.gameObject.transform.localScale.z; z < (this.gameObject.transform.localScale.z * 2); z++)
            {
                heights[mouseX - i, mouseZ - z] = y;
                terrainData.SetHeights(mouseX - i, mouseZ - z, modifiedHeights);
            }
        }


    }

//flatten quickly so we can re-level with our RaiseTerrain function
private void LowerTerrain(Vector3 point)
{
int mouseX = (int)((point.x / terrainData.size.x) * heightmapWidth);
int mouseZ = (int)((point.z / terrainData.size.z) * heightmapHeight);

        float[,] modifiedHeights = new float[1, 1];
        float y = heights[mouseX, mouseZ];
        y -= strength * Time.deltaTime;

        if (y < terrainData.size.y)
        {
            y = 0;
        }

        modifiedHeights[0, 0] = y;
        
        for (int i = -(int)this.gameObject.transform.localScale.x; i < (this.gameObject.transform.localScale.x * 2); i++)
        {
            for (int z = -(int)this.gameObject.transform.localScale.z; z < (this.gameObject.transform.localScale.z * 2); z++)
            {
                heights[mouseX - i, mouseZ - z] = y;
                terrainData.SetHeights(mouseX - i, mouseZ - z, modifiedHeights);
            }
        }

    }
}

Result: