Terrain editor brush (Solved)

Hello. I’m working on an in-game basic terrain editor similar to Unity’s. I want to be able to raise/lower the terrain with a circle brush, including properties (size, opacity), but didn’t find relevant code. I got the mouse position and tried to create a radius around it for changing terrain, but only got individual spikes in a circle shaped arrangement. Any ideas?

private void raiseTerrain(Vector3 point){
	Debug.Log(point.x);
	for (int i=0;i<=myTerrain.terrainData.size.x;i++){
		for (int j=0;j<=myTerrain.terrainData.size.z;j++){
			float xx=point.x-i;
			float zz=point.z-j;
			if(xx*xx+zz*zz<rad*rad){
				int terX =(int)((i / myTerrain.terrainData.size.x) * xResolution);
				int terZ =(int)((j / myTerrain.terrainData.size.z) * zResolution);
				float y = heights[terX,terZ];
				y += 0.001f;
				float[,] height = new float[1,1];
				height[0,0] = y;
				heights[terX,terZ] = y;
			        myTerrain.terrainData.SetHeights(terX, terZ, height);
			}
		}
	}
}

Edit: Thanks for the help alucardj, it works really nice, now I just need to modify it for my goals.

Here is an example script using the methods outlined in my comment.

For a feathered circle brush, you could calculate a modifier based on the calc.magnitude and the circleRadius, multiply paintWeight based on that modifier. I added the time between paint application to give Unity time to apply the heights, you can play with this time value or remove it.

I code easier in uJS, but if you have trouble converting it to C#, I can do that.

Warning : I use lots of comments and spaces :wink:

//-----------------------------------//
//  RealTimeTerrainHeightPainter.js  //
//  Written by Alucard Jay           //
//  2014/5/26                        //
//-----------------------------------//

#pragma strict


public var circleRadius : int = 12;
public var paintWeight : float = 0.001;
public var rayTimeInterval : float = 0.1;

private var rayTimer : float = 0;
private var rayHitPoint : Vector3;
private var heightmapPos : Vector3;


//  Persistant Functions
//	----------------------------------------------------------------------------


function Start() 
{
	GetTerrainData();
	
	ResetHeights(); // FOR TESTING, reset to flat terrain
}


function Update() 
{
	rayTimer += Time.deltaTime;
	
	if ( rayTimer < rayTimeInterval )
		return;
	
	rayTimer = 0;
	
	RaycastToTerrain();
	GetHeightmapPosition();
	
	if ( Input.GetMouseButton(0) && rayHitPoint != Vector3.zero )
		PaintCircle( heightmapPos );
}


//  Terrain Data
//	----------------------------------------------------------------------------


public var terrain : Terrain;
private var terrainData : TerrainData;
private var terrainSize : Vector3;
private var heightmapWidth : int;
private var heightmapHeight : int;
private var heightmapData : float[,];


function GetTerrainData()
{
	if ( !terrain )
		terrain = Terrain.activeTerrain;

	terrainData = terrain.terrainData;

	terrainSize = terrain.terrainData.size;

	heightmapWidth = terrain.terrainData.heightmapWidth;
	heightmapHeight = terrain.terrainData.heightmapHeight;

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


//  Other Functions
//	----------------------------------------------------------------------------


function RaycastToTerrain() 
{
	rayHitPoint = Vector3.zero;
	
	var hit : RaycastHit;
	var rayPos : Ray = Camera.main.ScreenPointToRay( Input.mousePosition );

	if ( Physics.Raycast( rayPos, hit, Mathf.Infinity ) ) // also consider a layermask to just the terrain layer
	{
		rayHitPoint = hit.point;
		Debug.DrawLine( Camera.main.transform.position, hit.point, Color.red, rayTimeInterval );
	}
}


function GetHeightmapPosition() 
{
	// find the heightmap position of that hit
	heightmapPos.x = ( rayHitPoint.x / terrainSize.x ) * parseFloat( heightmapWidth );
	heightmapPos.z = ( rayHitPoint.z / terrainSize.z ) * parseFloat( heightmapHeight );

	// convert to integer
	heightmapPos.x = Mathf.RoundToInt( heightmapPos.x );
	heightmapPos.z = Mathf.RoundToInt( heightmapPos.z );

	// clamp to heightmap dimensions to avoid errors
	heightmapPos.x = Mathf.Clamp( heightmapPos.x, 0, heightmapWidth - 1 );
	heightmapPos.z = Mathf.Clamp( heightmapPos.z, 0, heightmapHeight - 1 );
}


function PaintCircle( point : Vector3 ) 
{
	var x : int;
	var z : int;
	var heightX : int;
	var heightZ : int;
	var heightY : float;
	var calc : Vector2;
	
	for ( z = -circleRadius; z <= circleRadius; z ++ )
	{
		for ( x = -circleRadius; x <= circleRadius; x ++ )
		{
			// for a circle, calcualate a relative Vector2
			calc = new Vector2( x, z );
			// check if the magnitude is within the circle radius
			if ( calc.magnitude <= circleRadius )
			{
				// is within circle, paint height
				heightX = point.x + x;
				heightZ = point.z + z;
				
				// check if heightX and Z is within the heightmapData array size
				if ( heightX >= 0 && heightX < heightmapWidth && heightZ >= 0 && heightZ < heightmapHeight )
				{
					// read current height
					heightY = heightmapData[ heightZ, heightX ]; // note that in heightmapData, X and Z are reversed
					
					// add paintWeight to the current height
					heightY += paintWeight;
					
					// update heightmapData array
					heightmapData[ heightZ, heightX ] = heightY;
				}
			}
		}
	}
	
	// apply new heights to terrainData
	terrainData.SetHeights( 0, 0, heightmapData );
}


function ResetHeights() // FOR TESTING, reset to flat terrain
{
	var x : int;
	var z : int;
	
	for ( z = 0; z < heightmapHeight; z ++ )
	{
		for ( x = 0; x < heightmapWidth; x ++ )
		{
			heightmapData[ z, x ] = 0;
		}
	}
	
	terrainData.SetHeights( 0, 0, heightmapData );
}