Skip to content

Instantly share code, notes, and snippets.

@GibTreaty
Created October 5, 2016 14:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save GibTreaty/2b1e7a3cfc4dd2b255d9489363c9eaa3 to your computer and use it in GitHub Desktop.
Save GibTreaty/2b1e7a3cfc4dd2b255d9489363c9eaa3 to your computer and use it in GitHub Desktop.
Voxel based world generation
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);
}
}
}
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;
}
}
}
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);
}
}
}
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