Created
August 29, 2016 12:10
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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