Skip to content

Instantly share code, notes, and snippets.

@N-Carter
Created August 29, 2016 12:10
Show Gist options
  • Save N-Carter/79dc71e761d02d742f431a60f70e7ce1 to your computer and use it in GitHub Desktop.
Save N-Carter/79dc71e761d02d742f431a60f70e7ce1 to your computer and use it in GitHub Desktop.
A simple terrain generator for Unity. The noise function comes from here: https://code.google.com/p/simplexnoise/source/browse/trunk/SimplexNoise/Noise.cs
using UnityEngine;
using System.Collections;
using System.Threading;
using SimplexNoise;
public class TerrainGenerator : MonoBehaviour
{
[SerializeField] protected int m_Size = 512; // TODO: Require PoT sizes
[SerializeField] protected float m_Height = 32.0f;
[SerializeField] protected Texture2D[] m_Textures; // Must contain textures in multiples of four
[SerializeField] protected bool m_NoThreading;
[System.Serializable]
protected class NoiseLayer
{
public bool enabled = true;
public Vector2 scale = Vector2.one;
public Vector2 offset;
public float height = 1.0f;
public float clampMin = 0.0f;
public float clampMax = 1.0f;
}
[SerializeField] protected NoiseLayer[] m_NoiseLayers;
protected TerrainData m_TerrainData;
protected int m_SizeInSamples;
protected int m_NumSplatMaps;
protected object m_LockObject = new System.Object();
#region Events
protected void Start()
{
m_TerrainData = new TerrainData();
m_TerrainData.name = "Generated Terrain";
Generate();
}
#endregion
public void Generate()
{
m_SizeInSamples = m_Size + 1; // Should be PoT + 1, apparently
m_TerrainData.heightmapResolution = m_Size + 1;
m_TerrainData.size = new Vector3(m_Size, m_Height, m_Size);
// Splats:
m_NumSplatMaps = m_Textures.Length / 4 * 4;
var splatPrototypes = new SplatPrototype[4];
for(int i = 0; i < m_Textures.Length; ++i)
{
splatPrototypes[i] = new SplatPrototype();
splatPrototypes[i].texture = m_Textures[i];
}
splatPrototypes[0].tileSize = new Vector2(50.0f, 50.0f);
m_TerrainData.splatPrototypes = splatPrototypes;
// Heightmap:
var heights = new float[m_SizeInSamples, m_SizeInSamples];
TimedProfile.BeginSample("GenerateHeights");
GenerateHeights(heights);
TimedProfile.EndSample("GenerateHeights");
TimedProfile.BeginSample("SetHeights");
m_TerrainData.SetHeights(0, 0, heights);
TimedProfile.EndSample("SetHeights");
// Textures:
m_TerrainData.alphamapResolution = m_Size;
TimedProfile.BeginSample("MakeSplatMaps");
var splatMaps = GenerateSplatMaps(heights);
TimedProfile.EndSample("MakeSplatMaps");
TimedProfile.BeginSample("SetAlphamaps");
m_TerrainData.SetAlphamaps(0, 0, splatMaps);
TimedProfile.EndSample("SetAlphamaps");
// Terrain object:
var terrain = GetComponent<Terrain>();
if(terrain == null)
terrain = gameObject.AddComponent<Terrain>();
terrain.transform.position = new Vector3(m_Size * -0.5f, 0.0f, m_Size * -0.5f); // Centre on the origin
terrain.terrainData = m_TerrainData;
terrain.heightmapPixelError = 1;
// Collider:
TimedProfile.BeginSample("TerrainCollider");
var terrainCollider = GetComponent<TerrainCollider>();
if(terrainCollider == null)
terrainCollider = gameObject.AddComponent<TerrainCollider>();
terrainCollider.terrainData = m_TerrainData;
TimedProfile.EndSample("TerrainCollider");
TimedProfile.PrintResults();
}
#region Height generation functions
protected class Parameters
{
public float[,] heights;
public float[,,] splatMaps; // Only valid when generating splat maps
public int startY;
public int endY; // endY is the last row plus one
}
protected void ProcessHeights(float[,] heights, float[,,] splatMaps, System.Action<object> action)
{
int numThreads = (m_NoThreading ? 1 : SystemInfo.processorCount);
int stripWidth = m_Size / numThreads;
var threads = new Thread[numThreads];
for(int t = 0; t < numThreads; ++t)
{
if(!m_NoThreading)
threads[t] = new Thread(new ParameterizedThreadStart(action));
var data = new Parameters();
data.heights = heights;
data.splatMaps = splatMaps;
data.startY = t * stripWidth;
data.endY = data.startY + stripWidth;
if(m_NoThreading)
action(data);
else
threads[t].Start(data);
}
if(!m_NoThreading)
{
foreach(var thread in threads) // Wait for threads to finish
thread.Join(10000);
}
}
protected void GenerateHeights(float[,] heights)
{
ProcessHeights(heights, null, GenerateHeightsStrip);
// Need to do the last row manually because ProcessHeights doesn't take the odd size of the heightmap into account:
var data = new Parameters();
data.heights = heights;
data.startY = m_Size;
data.endY = m_Size + 1;
GenerateHeightsStrip(data);
}
protected void GenerateHeightsStrip(object parameters)
{
var data = (Parameters)parameters;
for(int y = data.startY; y < data.endY; ++y)
{
for(int x = 0; x < m_SizeInSamples; ++x)
data.heights[x, y] = CalculateHeight(x, y);
}
}
protected float CalculateHeight(int x, int y)
{
float value = 0.0f;
foreach(var layer in m_NoiseLayers)
{
if(!layer.enabled)
continue;
value += Mathf.Clamp(Noise.Generate(x * layer.scale.x + layer.offset.x, y * layer.scale.y + layer.offset.y) * 0.5f + 0.5f, layer.clampMin, layer.clampMax) * layer.height;
}
return Mathf.Clamp01(value / m_NoiseLayers.Length);
}
#endregion
#region Splat map generation functions
protected float[,,] GenerateSplatMaps(float[,] heights)
{
var splatMaps = new float[m_Size, m_Size, m_NumSplatMaps];
ProcessHeights(heights, splatMaps, GenerateSplatMapsStrip);
return splatMaps;
}
protected void GenerateSplatMapsStrip(object parameters)
{
var data = (Parameters)parameters;
var factors = new float[m_NumSplatMaps];
for(int y = data.startY; y < data.endY; ++y)
{
for(int x = 0; x < m_Size; ++x) // This needs to be m_Size here, but m_SizeInSamples for GenerateHeightsStrip
{
factors[0] = Mathf.Clamp01((data.heights[x, y] - 0.2f) * 2.0f);
factors[1] = Mathf.Clamp01(1.5f - data.heights[x, y] * 2.5f);
factors[2] = (Noise.Generate(x * 0.01f, y * 0.01f) * 0.5f + 0.5f);
NormaliseFloatArray(factors);
for(int i = 0; i < m_NumSplatMaps; ++i)
data.splatMaps[x, y, i] = factors[i];
}
}
}
#endregion
protected void NormaliseFloatArray(float[] floats)
{
float scale = 0.0f;
for(int i = 0; i < floats.Length; ++i)
scale += floats[i];
for(int i = 0; i < floats.Length; ++i)
floats[i] /= scale;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment