Turn a Terrain into a Contoured Map

Is there a way to create a height-contoured map from a Unity Terrain other than to manually paint over it? Something like Arma to this effect:

alt text

I’ve tried using an exported heightmap but Photoshop-fu can only take one so far.

Updated Post

Figured I may aswell try it in Unity… (Output shown on plane. Uses alpha channel so lines can be overlaid)

Code below image.

[32379-topo.png|32379]

Listing for HeightmapToContourMap.cs

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

public class ContourMap
{
	//Creates contour map from Raw 16bpp heightmap data as Texture2D
	//Returns null on failure
	//If neither width nor height specified, then it's POT size will be guessed
	public static Texture2D FromRawHeightmap16bpp( string fileName, int width = 0, int height = 0 )
	{
		if ( !File.Exists( fileName ) )
		{
			Debug.Log( "Heightmap not found " + fileName );
			return null;
		}

		//dimensions
		int _width = width;
		int _height = height;

		Color32 bandColor =  new Color32(255, 255, 255, 255);
		Color32 bkgColor = new Color32(0, 0, 0, 0);
		
		//Output
		Texture2D topoMap;

		//Read raw 16bit heightmap
		byte[] rawBytes = System.IO.File.ReadAllBytes( fileName );
		short[] rawImage = new short[rawBytes.Length / 2];

		//Create slice buffer
		bool[] slice = new bool[rawImage.Length];

		//Convert to bytes to short
		Buffer.BlockCopy( rawBytes, 0, rawImage, 0, rawBytes.Length );
			
		//Create Texture2D with estimated or specified width
		if ( _width == 0 || _height == 0 )
		{
			_width = (int)Math.Sqrt( rawImage.Length ); //Estimated width/height
			_height = _width;
			topoMap = new Texture2D( _width, _height );
		}
		else
		{
			topoMap = new Texture2D( _width, _height );
		}

		topoMap.anisoLevel = 16;

		//Set background
		for (int x = 0; x < _width; x++)
		{
			for (int y = 0; y < _height; y++)
			{
				topoMap.SetPixel( x, y, bkgColor );
			}
		}
			
		//Initial Min/Max values for signed 16bit value
		int minHeight = 32767;
		int maxHeight = -32767;

		//Find lowest and highest points
		for ( int i = 0; i < rawImage.Length; i++ )
		{
			if ( rawImage[ i ] < minHeight )
			{
				minHeight = rawImage[ i ];
			}
			if ( rawImage[ i ] > maxHeight )
			{
				maxHeight = rawImage[ i ];
			}
		}

		Debug.Log("Min: " + minHeight.ToString() + ", Max: " + maxHeight.ToString());

		//Create height band list
		int bandDistance = maxHeight / 12; //Number of height bands to create
			
		List<int> bands = new List<int>();
			
		//Get ranges
		int r = minHeight + bandDistance;
		while ( r < maxHeight )
		{
			bands.Add( r );
			r += bandDistance;
		}			
			
		//Draw bands
		for ( int b = 0; b < bands.Count; b++ )
		{
				
			//Get Slice
			for ( int i = 0; i < rawImage.Length; i++ )
			{
				if ( rawImage[ i ] >= bands[ b ] )
				{
					slice[ i ] = true;
				}
				else
				{
					slice[ i ] = false;
				}
			}
				
			//Detect edges on slice and write to output
			for ( int y = 1; y < _height - 1; y++ )
			{
				for ( int x = 1; x < _width - 1; x++ )
				{
					if ( slice[ y * _width + x ] == true )
					{
						if ( slice[ y * _width + ( x - 1 ) ] == false || slice[ y * _width + ( x + 1 ) ] == false || slice[ ( y - 1 ) * _width + x ] == false || slice[ ( y + 1 ) * _width + x ] == false )
						{
							topoMap.SetPixel( x, y, bandColor );
						}
					}
				}
			}
				
		}

		topoMap.Apply();
			
		//Return result
		return topoMap;
	}
}

Listing for TopoLines.cs

using UnityEngine;
using System.Collections;

public class TopoLines : MonoBehaviour
{
	public string heightmapPath = "/Users/dave/desktop/terrain.raw";

	public Texture2D topoMap;

	public Material outputMaterial;
	
	void Start()
	{
		topoMap = ContourMap.FromRawHeightmap16bpp(heightmapPath);

		if (topoMap == null)
		{
			Debug.Log("Creation of topomap failed.");
		}
		else
		{
			Debug.Log("Creation of topomap was successful.");
		}

		if (outputMaterial != null)
		{
			outputMaterial.mainTexture = topoMap;
		}
	}
}

This is a modified script from the excellent answer by Dave29483 for creating a contour map texture from a terrain. Add the function to that class, or just use the class as below.

Call with :

ContourMap.FromTerrain( terrain ) // use default number of bands and colours
ContourMap.FromTerrain( terrain, numberOfBands ) // assign number of bands, use default colours
ContourMap.FromTerrain( terrain, numberOfBands, bandColor, bkgColor ) // assign number of bands and colours

modified ContourMap.cs :

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


public class ContourMap : MonoBehaviour 
{
	// Creates contour map from terrain heightmap data as Texture2D
	
	// use default colours and optional parameter numberOfBands
	public static Texture2D FromTerrain( Terrain terrain, int numberOfBands = 12 ) 
	{
		return FromTerrain( terrain, numberOfBands, Color.white, Color.clear );
	}
	
	// define all parameters
	public static Texture2D FromTerrain( Terrain terrain, int numberOfBands, Color bandColor, Color bkgColor ) 
	{
		// dimensions
		int width = terrain.terrainData.heightmapWidth;
		int height = terrain.terrainData.heightmapHeight;
		
		// heightmap data
		float[,] heightmap = terrain.terrainData.GetHeights( 0, 0, width, height );
		
		// Create Output Texture2D with heightmap dimensions
		Texture2D topoMap = new Texture2D( width, height );
		topoMap.anisoLevel = 16;
		
		// array for storing colours to be applied to texture
		Color[] colourArray = new Color[ width * height ];
		
		// Set background
		for ( int y = 0; y < height; y++ )
		{
			for ( int x = 0; x < width; x++ )
			{
				colourArray[ (y * width) + x ] = bkgColor;
			}
		}
		
		// Initial Min/Max values for normalized terrain heightmap values
		float minHeight = 1f;
		float maxHeight = 0;
		
		// Find lowest and highest points
		for ( int y = 0; y < height; y++ )
		{
			for ( int x = 0; x < width; x++ )
			{
				if ( minHeight > heightmap[ y, x ] )
				{
					minHeight = heightmap[ y, x ];
				}
				if ( maxHeight < heightmap[ y, x ] )
				{
					maxHeight = heightmap[ y, x ];
				}
			}
		}
		
		// Create height band list
		float bandDistance = ( maxHeight - minHeight ) / (float)numberOfBands; // Number of height bands to create
		
		List< float > bands = new List< float >();
		
		// Get ranges
		float r = minHeight + bandDistance;
		while ( r < maxHeight )
		{
			bands.Add( r );
			r += bandDistance;
		}
		
		// Create slice buffer
		bool[,] slice = new bool[ width, height ];
		
		// Draw bands
		for ( int b = 0; b < bands.Count; b++ )
		{
			// Get Slice
			for ( int y = 0; y < height; y++ )
			{
				for ( int x = 0; x < width; x++ )
				{
					if ( heightmap[ y, x ] >= bands[ b ] )
					{
						slice[ x, y ] = true;
					}
					else
					{
						slice[ x, y ] = false;
					}
				}
			}
			
			// Detect edges on slice and write to output
			for ( int y = 1; y < height - 1; y++ )
			{
				for ( int x = 1; x < width - 1; x++ )
				{
					if ( slice[ x, y ] == true )
					{
						if ( 
							slice[ x - 1, y ] == false || 
							slice[ x + 1, y ] == false || 
							slice[ x, y - 1 ] == false || 
							slice[ x, y + 1 ] == false )
						{
							// heightmap is read y,x from bottom left
							// texture is read x,y from top left
							// magic equation to find correct array index
							int ind = ( ( height - y - 1 ) * width ) + ( width - x - 1 );
							
							colourArray[ ind ] = bandColor;
						}
					}
				}
			}
			
		}
		
		// apply colour array to texture
		topoMap.SetPixels( colourArray );
		topoMap.Apply();
		
		// Return result
		return topoMap;
	}
}

example TopoLines.cs :

using UnityEngine;
using System.Collections;


public class TopoLines : MonoBehaviour 
{
	public Terrain terrain;
	
	public int numberOfBands = 12;
	
	public Color bandColor = Color.white;
	public Color bkgColor = Color.clear;
	
	public Renderer outputPlain;
	
	public Texture2D topoMap;
	
	
	void Start() 
	{
		GenerateTopoLines();
	}
	
	void Update() 
	{
		if ( Input.GetMouseButtonDown(0) )
		{
			GenerateTopoLines();
		}
	}
	
	void GenerateTopoLines() 
	{
		//topoMap = ContourMap.FromTerrain( terrain );
		//topoMap = ContourMap.FromTerrain( terrain, numberOfBands );
		topoMap = ContourMap.FromTerrain( terrain, numberOfBands, bandColor, bkgColor );
		
		if ( outputPlain )
		{
			outputPlain.material.mainTexture = topoMap;
		}
	}
}

Image from test. Change values in runtime and left-mouse in scene to observe :

I am very new to unity but would like to implement this. Is there somewhere where I can get more instruction or even a sample project with this already setup?

Thanks.

I’ve made an asset “Wire Terrain” and it can create mesh contours from terrain: WireTerrain | Modeling | Unity Asset Store