-
Chang, Ryan authoredChang, Ryan authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
TerrainGenerator.cs 4.95 KiB
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Collections.Concurrent;
using System;
[System.Serializable]
public struct Octave
{
public float amplitude;
public float frequency;
public NoiseType type;
}
[System.Serializable]
public enum NoiseType
{
MathfPerlin,
MathematicsPerlin,
Simplex,
Celluar
}
public class TerrainGenerator : MonoBehaviour
{
[Header("User settings")]
public List<Octave> octaves;
[Tooltip("The seed of the generation.")]
public Vector2 crawlStartPosition;
public bool generateNewSeed = true;
public bool refresh;
[Tooltip("How fine grain the generated terrain will be. " +
"Larger values = more precise. Recommended value is 513.")]
public int samples = 513;
[Tooltip("How tall the terrain is. May be changed to allow for higher terrains.")]
public float terrainHeight = 600;
[Tooltip("What percentage of threads available should we use?")]
public float threadsUsage = 0.7f;
public int MaxThreads => Mathf.FloorToInt(Environment.ProcessorCount * threadsUsage);
[Header("Autogenerated values")]
[Tooltip("Which terrain grid are we on right now?")]
public Vector2Int currentGridPosition;
[Tooltip("Array of terrains to reuse.")]
public Terrain[] terrains = new Terrain[9];
[Tooltip("Dictionary of previously loaded terrains.")]
public Dictionary<Vector2Int, TerrainData> previouslyLoaded = new();
public Queue<TerrainGenThread> queuedThreads = new();
public ConcurrentBag<TerrainGenThread> completedGenerators = new();
[Header("Stats")]
public int runningThreads = 0;
/// <summary>
/// Apply the terrain that was generated by genThread.
/// </summary>
/// <param name="genThread">The terrain gen thread that has finished
/// generating its terrain heights.</param>
private void ApplyGeneratedTerrain(TerrainGenThread genThread)
{
genThread.TerrainData.SetHeights(0, 0, genThread.GetHeights());
GameObject obj = Terrain.CreateTerrainGameObject(genThread.TerrainData);
obj.name = genThread.InitialCrawlPosition.ToString() + "Terrain";
obj.layer = LayerMask.NameToLayer("Terrain");
obj.transform.SetParent(transform, false);
Vector3 offset = new Vector3(genThread.GridPosition.x, 0, genThread.GridPosition.y) *
genThread.TerrainData.size.x;
obj.transform.position += offset;
}
private void GenerateThread(Vector2 crawlPos, Vector2Int gridPos,
bool forceRefresh)
{
if (!previouslyLoaded.ContainsKey(gridPos) || forceRefresh)
{
TerrainData data = Instantiate(Resources.Load<TerrainData>("Template"));
TerrainGenThread tGenThread = new(this, data, crawlPos, gridPos);
previouslyLoaded[gridPos] = data;
if (runningThreads >= MaxThreads)
{
// Too many threads.
queuedThreads.Enqueue(tGenThread);
}
else
{
StartThread(tGenThread);
}
}
}
private void StartThread(TerrainGenThread tGenThread)
{
runningThreads++;
ThreadStart starter = new(tGenThread.Generate);
Thread thread = new(starter);
thread.Start();
}
public virtual void GenerateSurronding(Vector2 initCrawlPos, bool forceRefresh = false)
{
int count = 0;
for (int i = -1; i <= 1; i++)
{
for (int j = 1; j >= -1; j--)
{
GenerateThread(currentGridPosition + initCrawlPos + new Vector2(i, j),
currentGridPosition + new Vector2Int(i, j),
forceRefresh);
count++;
}
}
}
private void Update()
{
// Check for refresh.
if (refresh)
{
refresh = false;
foreach (Transform child in transform)
Destroy(child.gameObject);
GenerateSurronding(crawlStartPosition, true);
}
// Check for queued threads.
if (queuedThreads.TryDequeue(out TerrainGenThread queuedThread)
&& runningThreads < MaxThreads)
{
StartThread(queuedThread);
}
// Check for completed generators.
if (completedGenerators.TryTake(out TerrainGenThread completedThread))
{
ApplyGeneratedTerrain(completedThread);
runningThreads--;
}
}
/// <summary>
/// Gets the square distance between two points.
/// If A is located at (0,0) and B is located at (5,-10),
/// the distance would be 5+10=15.
/// </summary>
/// <param name="a">A position on the grid.</param>
/// <param name="b">Another position.</param>
/// <returns></returns>
public int SquareDistance(Vector2Int a, Vector2Int b)
{
Vector2Int diff = b - a;
return Mathf.Abs(diff.x) + Mathf.Abs(diff.y);
}
}