- Home /
 
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.
Ins$$anonymous$$d of iterating through loops of the terrainData.size, use the heightmapData
 heightmapData = terrainData.GetHeights( 0, 0, heightmapWidth, heightmapHeight );
 
                  now you have a 2D array of the heightmapData, modify that, then reapply to the terrainData when you have finished modifying the whole circle area
 terrainData.SetHeights( 0, 0, heightmapData );
 
                  You'll need to convert your point from terrain coordinate to heightmapData coordinate. If this is what you're already doing, it's really hard to tell with that highly compacted un-commented code.
Yeah, it's as you say. But still can't change the terrain in a circle shape, it's more like a line. I think I don't get the heightmapData indexes right and their meaning.
Answer by AlucardJay · May 25, 2014 at 04:08 PM
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 );
 }
 
              Your answer
 
             Follow this Question
Related Questions
Problem detail resolution per patch. 2 Answers
Terrain generation [2D,C#]? 0 Answers
SetHeights to a Negative 1 Answer
terraindata.setheight as a brush for runtime terrain editing 1 Answer