I have a ground mesh, and I want to use terrain to quickly place details on it, so I need to create a terrain height map that matches the mesh. I couldn’t find anything for this, so I wrote a script. Thought I’d post it in case it’s useful to anyone. To use it, create a terrain, line it up below the mesh (terrain only goes up, so don’t put it above), select the terrain and run the script. It’ll shape against all colliders, so it’s quickest to create a temporary scene to do this in so it doesn’t hit other objects.
using UnityEngine;
using UnityEditor;
// Set the height map of terrain to collider hits. This allows matching terrain
// to a mesh.
public class SetTerrainFromMesh
{
[MenuItem("Edit/Match terrain to mesh")]
static void Go()
{
if(Selection.activeTransform == null)
{
Debug.LogWarning("Select the terrain");
return;
}
Terrain terrain = Selection.activeTransform.GetComponent<Terrain>();
if(terrain == null)
{
Debug.LogWarning("The selection isn't a terrain");
return;
}
// Temporarily disable the terrain collider, so our rays don't hit it.
TerrainCollider terrainCollider = terrain.gameObject.GetComponent<TerrainCollider>();
bool terrainColliderWasEnabled = terrainCollider.enabled;
if(terrainCollider != null)
terrainCollider.enabled = false;
TerrainData terrainData = terrain.terrainData;
Vector3 worldSize = new Vector3(terrainData.size.z, 0, terrainData.size.x);
float[,] heights = terrainData.GetHeights(0, 0, terrainData.heightmapWidth, terrainData.heightmapHeight);
for(int x = 0; x < terrainData.heightmapWidth; ++x)
{
for(int y = 0; y < terrainData.heightmapHeight; ++y)
{
// The X axis of terrain is along the Z axis in object space, and the Y axis of
// terrain is along the X axis in object space.
float worldZ = scale(x, 0, terrainData.heightmapWidth, 0, worldSize.z);
float worldX = scale(y, 0, terrainData.heightmapHeight, 0, worldSize.x);
// Cast a ray downwards from above the terrain.
Vector3 worldPos = new Vector3(worldX, 10000, worldZ);
worldPos += terrain.transform.position;
Ray ray = new Ray(worldPos, Vector3.down);
RaycastHit hit;
if(!Physics.Raycast(ray, out hit))
{
heights[x,y] = 0;
continue;
}
Vector3 hitPoint = hit.point;
float hitY = hitPoint.y;
hitY -= terrain.transform.position.y;
heights[x,y] = hitY / terrainData.size.y;
}
}
terrainData.SetHeights(0, 0, heights);
// Restore the terrain collider.
if(terrainCollider != null)
terrainCollider.enabled = terrainColliderWasEnabled;
}
static public float scale(float x, float l1, float h1, float l2, float h2)
{
return (x - l1) * (h2 - l2) / (h1 - l1) + l2;
}
}
Then you can disable terrain drawing, so it only draws details and trees.
Terrain is pretty unpolished:
- To paint a reasonable number of detail meshes, I need to set a really low opacity, like 0.02. I can only set that by typing it in the input box (too low to set it with the slider).
- To erase details I have to set the opacity up higher again, then set it back manually, which makes the draw-erase-draw editing cycle a tedious headache. Draw and erase opacity should be stored separately.
- You can only set the tree brush size down to 1, which is much too big.
- You have no control over tree rotations. They just all drop in the same direction, and there’s no “grooming” to paint rotations. This one’s a killer.
- “Random Tree Rotation” only shows up if there’s an LOD group, which is a strange bug.