# 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

``````//-----------------------------------//
//  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 );
}
``````