Created
October 5, 2016 14:20
-
-
Save GibTreaty/2b1e7a3cfc4dd2b255d9489363c9eaa3 to your computer and use it in GitHub Desktop.
Voxel based world generation
This file contains 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; | |
namespace YounGenTech.VoxelTech { | |
[System.Serializable] | |
public struct Block { | |
public static Block Empty { get { return new Block(0); } } | |
public static Block Air { get { return new Block(1); } } | |
public static Block Solid { get { return new Block(2); } } | |
[SerializeField] | |
byte _id; | |
#region Properties | |
public byte ID { | |
get { return _id; } | |
set { _id = value; } | |
} | |
#endregion | |
public Block(byte id) { | |
_id = id; | |
} | |
public override bool Equals(object obj) { | |
if(obj.GetType() == typeof(Block)) { | |
Block block = (Block)obj; | |
return block.ID == ID; | |
} | |
return false; | |
} | |
public override int GetHashCode() { | |
return base.GetHashCode(); | |
} | |
public static bool operator ==(Block a, Block b) { | |
return a.Equals(b); | |
} | |
public static bool operator !=(Block a, Block b) { | |
return !a.Equals(b); | |
} | |
} | |
} |
This file contains 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 System; | |
using System.Collections.Generic; | |
using UnityEngine; | |
namespace YounGenTech.VoxelTech { | |
//[System.Serializable] | |
public class Chunk { | |
Block[] _blocks; | |
VectorI3 _size; | |
#region Properties | |
public bool HasChanged { get; protected set; } | |
public World InWorld { get; private set; } | |
public VectorI3 Position { get; set; } | |
public VectorI3 Size { | |
get { return _size; } | |
set { _size = value; } | |
} | |
public int TotalSize { get; private set; } | |
#endregion | |
public Chunk(World world, VectorI3 size) { | |
_size = size; | |
TotalSize = _size.size; | |
_blocks = new Block[TotalSize]; | |
InWorld = world; | |
} | |
public Block GetBlock(int index) { | |
return _blocks[index]; | |
} | |
public Block GetBlock(VectorI3 index) { | |
return _blocks[index.FlatIndex(Size)]; | |
} | |
public Block GetBlockNeighbor(int index, CubeDirectionFlag direction) { | |
return GetBlockNeighbor(index, direction.ToDirectionVector()); | |
} | |
public Block GetBlockNeighbor(int index, VectorI3 direction) { | |
var position = index.FlatTo3DIndex(Size) + direction; | |
int neighborIndex = position.FlatIndex(Size); | |
return !position.ArrayOutOfBounds(Size) ? | |
_blocks[neighborIndex] : | |
InWorld.GetBlock(Position + position); | |
} | |
public List<BlockNeighbor> GetBlockNeighbors(int index, CubeDirectionFlag directions) { | |
List<BlockNeighbor> blocks = new List<BlockNeighbor>(); | |
for(int i = 0; i < 26; i++) | |
if(((int)directions & (1 << (i + 1))) != 0) { | |
var newDirection = i.ToCubeDirection(); | |
var block = GetBlockNeighbor(index, newDirection); | |
blocks.Add(new BlockNeighbor(block, newDirection)); | |
} | |
return blocks; | |
} | |
public void LoadBlocks(Vector3 position, Func<Vector3, Block> noiseMethod) { | |
for(int i = 0; i < TotalSize; i++) | |
_blocks[i] = noiseMethod(position + i.FlatTo3DIndex(Size)); | |
} | |
public void SetBlock(int index, Block block) { | |
if(_blocks[index] != block) | |
HasChanged = true; | |
_blocks[index] = block; | |
} | |
} | |
public struct BlockNeighbor { | |
public Block block; | |
public CubeDirectionFlag direction; | |
public BlockNeighbor(Block block, CubeDirectionFlag direction) { | |
this.block = block; | |
this.direction = direction; | |
} | |
} | |
} |
This file contains 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 System; | |
using System.Collections.Generic; | |
using UnityEngine; | |
namespace YounGenTech.VoxelTech { | |
public class ChunkObject : MonoBehaviour { | |
[SerializeField] | |
Mesh _chunkMesh; | |
[SerializeField] | |
MeshFilter _chunkMeshFilter; | |
[SerializeField] | |
MeshRenderer _chunkRenderer; | |
[SerializeField] | |
MeshCollider _chunkCollider; | |
#region Properties | |
public MeshCollider ChunkCollider { | |
get { return _chunkCollider; } | |
set { _chunkCollider = value; } | |
} | |
public Chunk ChunkData { get; set; } | |
public Mesh ChunkMesh { | |
get { return _chunkMesh; } | |
set { _chunkMesh = value; } | |
} | |
public MeshFilter ChunkMeshFilter { | |
get { return _chunkMeshFilter; } | |
set { _chunkMeshFilter = value; } | |
} | |
public MeshRenderer ChunkRenderer { | |
get { return _chunkRenderer; } | |
set { _chunkRenderer = value; } | |
} | |
public World InWorld { get; set; } | |
#endregion | |
public void CalculateMesh() { | |
if(ChunkData == null) { | |
Debug.LogError("No ChunkData Found!"); | |
return; | |
} | |
if(ChunkMesh == null) { | |
ChunkMesh = new Mesh(); | |
ChunkMesh.MarkDynamic(); | |
ChunkMeshFilter.sharedMesh = ChunkMesh; | |
ChunkCollider.sharedMesh = ChunkMesh; | |
ChunkMesh.subMeshCount = 1; | |
} | |
MeshData meshData = new MeshData() { | |
vertices = new List<Vector3>(), | |
colors = new List<Color>(), | |
normals = new List<Vector3>(), | |
indices = new Dictionary<int, List<int>>(), | |
uv = new List<Vector2>() | |
}; | |
for(int i = 0; i < ChunkData.TotalSize; i++) | |
if(ChunkData.GetBlock(i).ID > 1) { | |
var blockPosition = i.FlatTo3DIndex(ChunkData.Size); | |
var generateMeshDirections = CubeDirectionFlag.None; | |
var directions = CubeDirectionFlag.Faces; | |
var blockNeighbors = ChunkData.GetBlockNeighbors(i, directions); | |
foreach(var blockNeighbor in blockNeighbors) | |
if(blockNeighbor.block.ID <= 1) | |
generateMeshDirections |= blockNeighbor.direction; | |
if(generateMeshDirections != CubeDirectionFlag.None) | |
CubeData.QuickCube(meshData, blockPosition - (ChunkData.Size * .5f) + (Vector3.one * .5f), generateMeshDirections); | |
} | |
meshData.ApplyTo(ChunkMesh); | |
} | |
void OnDrawGizmos() { | |
if(!Application.isPlaying) return; | |
Gizmos.color = Color.green; | |
Gizmos.DrawWireCube(transform.position, ChunkData.Size); | |
} | |
public void SetActive(bool value) { | |
gameObject.SetActive(value); | |
} | |
} | |
} |
This file contains 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 System.Collections.Generic; | |
using System.Diagnostics; | |
using UnityEngine; | |
namespace YounGenTech.VoxelTech { | |
public class World : MonoBehaviour { | |
[SerializeField] | |
VectorI3 _defaultWorldSize = new VectorI3(3, 3, 3); | |
[SerializeField] | |
ChunkSpawnStyle _chunkSpawning = ChunkSpawnStyle.Square; | |
[SerializeField] | |
VectorI3 _defaultChunkSize = new VectorI3(8, 8, 8); | |
[SerializeField] | |
GameObject _chunkObjectPrefab; | |
[SerializeField] | |
Transform _spawnPosition; | |
[SerializeField] | |
int _poolChunksAtStart = 30; | |
//[SerializeField] | |
//AnimationCurve anim = new AnimationCurve(); | |
Queue<ChunkObject> _chunkPool = new Queue<ChunkObject>(); | |
Dictionary<VectorI3, ChunkObject> _activeChunks = new Dictionary<VectorI3, ChunkObject>(); | |
List<ChunkObject> _chunksToRemove = new List<ChunkObject>(); | |
#region Properties | |
public VectorI3 DefaultChunkSize { | |
get { return _defaultChunkSize; } | |
private set { _defaultChunkSize = value; } | |
} | |
public VectorI3 DefaultWorldSize { | |
get { return _defaultWorldSize; } | |
private set { _defaultWorldSize = value; } | |
} | |
public Transform SpawnPosition { | |
get { return _spawnPosition; } | |
private set { _spawnPosition = value; } | |
} | |
public float WorldChunkRadius { | |
get { return Mathf.Max(DefaultWorldSize.x, DefaultWorldSize.z); } | |
} | |
#endregion | |
void Start() { | |
StartWorld(); | |
} | |
void Update() { | |
float radius = WorldChunkRadius * Mathf.Max(DefaultChunkSize.x, DefaultChunkSize.z) * .5f; | |
return; | |
foreach(var chunk in _activeChunks) { | |
Vector3 finalPosition = new Vector3(chunk.Value.ChunkData.Position.x, 0, chunk.Value.ChunkData.Position.z); | |
switch(_chunkSpawning) { | |
case ChunkSpawnStyle.Square: | |
Vector3 spawnPosition = GetPosition(_spawnPosition.position, PositionStyle.Chunk); | |
//if() | |
// RepoolChunk(chunk.Value); | |
break; | |
case ChunkSpawnStyle.Circle: | |
if((finalPosition - _spawnPosition.position).sqrMagnitude > (radius * radius)) | |
RepoolChunk(chunk.Value); | |
break; | |
} | |
} | |
if(_chunksToRemove.Count > 0) { | |
foreach(var chunk in _chunksToRemove) | |
_activeChunks.Remove(chunk.ChunkData.Position); | |
_chunksToRemove.Clear(); | |
} | |
} | |
ChunkObject CreateChunkObject() { | |
GameObject gameObject = Instantiate(_chunkObjectPrefab); | |
gameObject.transform.SetParent(transform, false); | |
var chunkObject = gameObject.GetComponent<ChunkObject>(); | |
chunkObject.InWorld = this; | |
return chunkObject; | |
} | |
public void GenerateChunkMeshes() { | |
long totalTime = 0; | |
int count = 0; | |
long longestTime = long.MinValue; | |
long shortestTime = long.MaxValue; | |
foreach(var chunk in _activeChunks) { | |
var trackMeshGeneration = Stopwatch.StartNew(); | |
chunk.Value.CalculateMesh(); | |
trackMeshGeneration.Stop(); | |
totalTime += trackMeshGeneration.ElapsedMilliseconds; | |
if(trackMeshGeneration.ElapsedMilliseconds > longestTime) | |
longestTime = trackMeshGeneration.ElapsedMilliseconds; | |
if(trackMeshGeneration.ElapsedMilliseconds < shortestTime) | |
shortestTime = trackMeshGeneration.ElapsedMilliseconds; | |
count++; | |
chunk.Value.SetActive(true); | |
} | |
UnityEngine.Debug.Log("Generate Chunk Meshes " + ((totalTime / (float)count)) + "ms\nLongest Time " + longestTime + "ms\nShortest Time " + shortestTime + "ms"); | |
} | |
public Block GetBlock(VectorI3 position) { | |
VectorI3 chunkPosition = GetPosition(position, PositionStyle.Chunk); | |
if(_activeChunks.ContainsKey(chunkPosition)) | |
return _activeChunks[chunkPosition].ChunkData.GetBlock(position - chunkPosition); | |
return Block.Empty; | |
} | |
public Block GetBlockNeighbor(VectorI3 position, CubeDirectionFlag direction) { | |
return GetBlock(position + direction.ToDirectionVector()); | |
} | |
public List<BlockNeighbor> GetBlockNeighbors(VectorI3 position, CubeDirectionFlag directions) { | |
List<BlockNeighbor> blocks = new List<BlockNeighbor>(); | |
for(int i = 0; i < 26; i++) | |
if(((int)directions & (1 << (i + 1))) != 0) { | |
var newDirection = i.ToCubeDirection(); | |
var block = GetBlockNeighbor(position, newDirection); | |
blocks.Add(new BlockNeighbor(block, newDirection)); | |
} | |
return blocks; | |
} | |
public ChunkObject GetChunkObject(VectorI3 position) { | |
ChunkObject chunkObject; | |
_activeChunks.TryGetValue(position, out chunkObject); | |
return chunkObject; | |
} | |
public ChunkObject GetChunkObjectNeighbor(VectorI3 position, CubeDirectionFlag direction) { | |
return GetChunkObject(position + direction.ToDirectionVector() * DefaultChunkSize); | |
} | |
public List<ChunkNeighbor> GetChunkObjectNeighbors(VectorI3 position, CubeDirectionFlag directions) { | |
List<ChunkNeighbor> chunks = new List<ChunkNeighbor>(); | |
for(int i = 0; i < 26; i++) | |
if(((int)directions & (1 << (i + 1))) != 0) { | |
var newDirection = i.ToCubeDirection(); | |
var newPosition = position + (DirectionHelper.cubeDirections[i] * DefaultChunkSize); | |
var chunkObject = GetChunkObject(newPosition); | |
if(chunkObject) | |
chunks.Add(new ChunkNeighbor(chunkObject, newDirection)); | |
} | |
return chunks; | |
} | |
public Vector3 GetPosition(Vector3 position, PositionStyle style, Pivot pivot = Pivot.Corner) { | |
switch(style) { | |
case PositionStyle.Chunk: return VectorI3.GridFloor(position, DefaultChunkSize) + (pivot == Pivot.Center ? DefaultChunkSize * .5f : Vector3.zero); | |
default: return VectorI3.GridFloor(position, VectorI3.one) + (pivot == Pivot.Center ? new Vector3(.5f, .5f, .5f) : Vector3.zero); | |
} | |
} | |
public static Block NoiseDensity(Vector3 position) { | |
float density = SimplexNoise.Noise(position.x * .066f, position.y * .066f, position.z * .066f) * 20.40f; | |
float density2 = SimplexNoise.Noise(position.x * .066f, position.y * .066f, position.z * .066f) * 100.40f; | |
density += 20; | |
density -= Mathf.Max(density2, 0); | |
density = position.y - density; | |
if(density <= 0) return Block.Solid; | |
else return Block.Air; | |
} | |
void OnDrawGizmos() { | |
Gizmos.color = Color.blue; | |
switch(_chunkSpawning) { | |
case ChunkSpawnStyle.Square: | |
//Gizmos.DrawWireCube(GetPosition(new Vector3(_spawnPosition.position.x, 0, _spawnPosition.position.z) + (new Vector3(DefaultChunkSize.x, 0, DefaultChunkSize.z) * .5f), PositionStyle.Chunk, Pivot.Center) - new Vector3(0, DefaultChunkSize.y*.5f, 0), (DefaultWorldSize * DefaultChunkSize) + Vector3.one * .01f); | |
Gizmos.DrawWireCube(VectorI3.GridFloor(_spawnPosition.position, DefaultChunkSize) + new Vector3(DefaultChunkSize.x, 0, DefaultChunkSize.z) * .5f, (DefaultWorldSize * DefaultChunkSize) + Vector3.one * .01f); | |
break; | |
case ChunkSpawnStyle.Circle: | |
Gizmos.DrawWireSphere(VectorI3.GridFloor(_spawnPosition.position, DefaultChunkSize) + new Vector3(DefaultChunkSize.x, 0, DefaultChunkSize.z) * .5f, (WorldChunkRadius * Mathf.Max(DefaultChunkSize.x, DefaultChunkSize.z)) * .5f); | |
break; | |
} | |
} | |
public void PoolChunks() { | |
for(int i = 0; i < _poolChunksAtStart; i++) | |
_chunkPool.Enqueue(CreateChunkObject()); | |
} | |
public void RepoolChunk(VectorI3 position) { | |
ChunkObject chunkObject; | |
if(_activeChunks.TryGetValue(position, out chunkObject)) | |
RepoolChunk(chunkObject); | |
} | |
public void RepoolChunk(ChunkObject chunkObject) { | |
if(_activeChunks.ContainsKey(chunkObject.ChunkData.Position)) { | |
chunkObject.SetActive(false); | |
_chunkPool.Enqueue(chunkObject); | |
_chunksToRemove.Add(chunkObject); | |
} | |
} | |
public ChunkObject SpawnChunk(Vector3 position) { | |
ChunkObject chunkObject = _chunkPool.Dequeue(); | |
if(chunkObject) { | |
if(chunkObject.ChunkData == null) | |
chunkObject.ChunkData = new Chunk(this, DefaultChunkSize); | |
chunkObject.transform.position = position + (DefaultChunkSize * .5f); | |
chunkObject.ChunkData.Position = position; | |
chunkObject.ChunkData.LoadBlocks(chunkObject.transform.position, NoiseDensity); | |
} | |
return chunkObject; | |
} | |
public void SpawnChunks() { | |
if(_activeChunks.Count > 0) return; | |
switch(_chunkSpawning) { | |
case ChunkSpawnStyle.Square: | |
for(int i = 0; i < DefaultWorldSize.size; i++) { | |
VectorI3 position = (i.FlatTo3DIndex(DefaultWorldSize) * DefaultChunkSize); | |
VectorI3 realPosition = VectorI3.GridFloor(_spawnPosition.position + (DefaultChunkSize * .5f) + position - (DefaultChunkSize * DefaultWorldSize * .5f), DefaultChunkSize); | |
if(!_activeChunks.ContainsKey(position)) | |
_activeChunks[realPosition] = SpawnChunk(realPosition); | |
} | |
break; | |
case ChunkSpawnStyle.Circle: | |
float radius = WorldChunkRadius * Mathf.Max(DefaultChunkSize.x, DefaultChunkSize.z) * .5f; | |
for(int i = 0; i < DefaultWorldSize.size; i++) { | |
VectorI3 position = i.FlatTo3DIndex(DefaultWorldSize) * DefaultChunkSize; | |
VectorI3 realPosition = VectorI3.GridFloor(_spawnPosition.position + (DefaultChunkSize * .5f) + position - (DefaultChunkSize * DefaultWorldSize * .5f), DefaultChunkSize); | |
Vector3 finalPosition = new Vector3(position.x, 0, position.z) + new Vector3(DefaultChunkSize.x * .5f, 0, DefaultChunkSize.z * .5f) - (new VectorI3(DefaultWorldSize.x, 0, DefaultWorldSize.z) * new VectorI3(DefaultChunkSize.x, 0, DefaultChunkSize.z) * .5f); | |
//if(finalPosition.magnitude <= (WorldChunkRadius * Mathf.Max(DefaultChunkSize.x, DefaultChunkSize.z) * .5f)) | |
// UnityEngine.Debug.DrawRay(realPosition + DefaultChunkSize * .5f, Vector3.up * DefaultChunkSize.y, Color.blue, 10, false); | |
if(!_activeChunks.ContainsKey(position)) | |
if(finalPosition.sqrMagnitude <= (radius * radius)) | |
_activeChunks[realPosition] = SpawnChunk(realPosition); | |
} | |
break; | |
} | |
} | |
public void StartWorld() { | |
PoolChunks(); | |
SpawnChunks(); | |
GenerateChunkMeshes(); | |
} | |
//void Update() { | |
// anim.AddKey(Time.time, SimplexNoise.Noise(Time.time * .066f * 4, 0, 0) * 2.40f); | |
//} | |
} | |
public struct ChunkNeighbor { | |
public ChunkObject chunkObject; | |
public CubeDirectionFlag direction; | |
public ChunkNeighbor(ChunkObject chunkObject, CubeDirectionFlag direction) { | |
this.chunkObject = chunkObject; | |
this.direction = direction; | |
} | |
} | |
public enum Pivot { | |
Corner, | |
Center | |
} | |
public enum PositionStyle { | |
Chunk, | |
Block | |
} | |
public enum ChunkSpawnStyle { | |
Square, | |
Circle | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment