345 lines
13 KiB
C#
345 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using UnityEngine;
|
|
using Random = UnityEngine.Random;
|
|
|
|
namespace Terrain {
|
|
public class World : MonoBehaviour {
|
|
public static Settings Settings;
|
|
|
|
[Header("World Generation Values")]
|
|
public BiomeAttributes[] biomes;
|
|
|
|
[Range(0.0f, 1.0f)] public float globalLightLevel;
|
|
public Color day;
|
|
public Color night;
|
|
|
|
public Transform player;
|
|
public Vector3 spawn;
|
|
public Clouds clouds;
|
|
|
|
public Material material;
|
|
public Material transparent;
|
|
public BlockType[] blockTypes;
|
|
|
|
[Header("UI")]
|
|
public GameObject creativeInventory;
|
|
public GameObject cursorSlot;
|
|
|
|
private readonly Chunk[,] _chunks = new Chunk[WorldData.WorldSizeInChunks, WorldData.WorldSizeInChunks];
|
|
private readonly List<Vector2Int> _activeChunks = new List<Vector2Int>();
|
|
private readonly List<Vector2Int> _chunksToCreate = new List<Vector2Int>();
|
|
public readonly List<Chunk> ChunksToUpdate = new List<Chunk>();
|
|
private readonly Queue<Queue<BlockMod>> _modificationQueues = new Queue<Queue<BlockMod>>();
|
|
private bool _applyingModifications;
|
|
private bool _menuOpen;
|
|
|
|
public readonly Queue<Chunk> ChunksToDraw = new Queue<Chunk>();
|
|
|
|
public Vector2Int lastPlayerChunk;
|
|
public readonly int[,] BlockBiomes = new int[WorldData.WorldSizeInBlocks, WorldData.WorldSizeInBlocks];
|
|
|
|
private Thread _chunkUpdateThread;
|
|
public readonly object ChunkUpdateThreadLock = new object();
|
|
|
|
private void Awake() {
|
|
Settings ??= Settings.Load("settings.json");
|
|
}
|
|
|
|
private void Start() {
|
|
Random.InitState(WorldData.Seed);
|
|
|
|
Shader.SetGlobalFloat("minGlobalLightLevel", WorldData.MinLightLevel);
|
|
Shader.SetGlobalFloat("maxGlobalLightLevel", WorldData.MaxLightLevel);
|
|
|
|
spawn = new Vector3(WorldData.WorldCenter, WorldData.ChunkSize.y - 50, WorldData.WorldCenter);
|
|
lastPlayerChunk = WorldData.GetChunkPositionFromWorldPosition(spawn);
|
|
|
|
if (Settings.enableThreading) {
|
|
_chunkUpdateThread = new Thread(ThreadedUpdate);
|
|
_chunkUpdateThread.Start();
|
|
}
|
|
|
|
GenerateWorld();
|
|
CheckViewDistance(true);
|
|
SetGlobalLightValue();
|
|
}
|
|
|
|
private void Update() {
|
|
CheckViewDistance();
|
|
|
|
if (_chunksToCreate.Count > 0)
|
|
CreateChunk();
|
|
|
|
if (ChunksToDraw.Count > 0) {
|
|
if (ChunksToDraw.Peek().Editable)
|
|
ChunksToDraw.Dequeue().CreateMesh();
|
|
}
|
|
|
|
if (!Settings.enableThreading) {
|
|
if (!_applyingModifications)
|
|
ApplyModifications();
|
|
if (ChunksToUpdate.Count > 0)
|
|
UpdateChunks();
|
|
}
|
|
}
|
|
|
|
public void SetGlobalLightValue() {
|
|
Shader.SetGlobalFloat("GlobalLightLevel", globalLightLevel);
|
|
Camera.main.backgroundColor = Color.Lerp(night, day, globalLightLevel);
|
|
}
|
|
|
|
public bool MenuOpen {
|
|
get => _menuOpen;
|
|
set {
|
|
_menuOpen = value;
|
|
if (_menuOpen) {
|
|
Cursor.lockState = CursorLockMode.None;
|
|
Cursor.visible = true;
|
|
creativeInventory.SetActive(true);
|
|
cursorSlot.SetActive(true);
|
|
} else {
|
|
Cursor.lockState = CursorLockMode.Locked;
|
|
Cursor.visible = false;
|
|
creativeInventory.SetActive(false);
|
|
cursorSlot.SetActive(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool CheckForBlock(in Vector3 block) {
|
|
Vector2Int chunkPos = WorldData.GetChunkPositionFromWorldPosition(block);
|
|
Chunk chunk = _chunks[chunkPos.x, chunkPos.y];
|
|
|
|
if (!IsBlockInWorld(block) || block.y < 0 || block.y > WorldData.ChunkSize.y)
|
|
return false;
|
|
|
|
if (chunk != null && chunk.Editable)
|
|
return blockTypes[chunk.GetBlockFromWorldPosition(block).Block].solid;
|
|
|
|
return blockTypes[GenerateBlock(block)].solid;
|
|
}
|
|
|
|
public BlockState GetBlock(in Vector3 block) {
|
|
Vector2Int chunkPos = WorldData.GetChunkPositionFromWorldPosition(block);
|
|
Chunk chunk = _chunks[chunkPos.x, chunkPos.y];
|
|
|
|
if (!IsBlockInWorld(block) || block.y < 0 || block.y > WorldData.ChunkSize.y)
|
|
return new BlockState(BlockType.Air);
|
|
|
|
if (chunk != null && chunk.Editable)
|
|
return chunk.GetBlockFromWorldPosition(block);
|
|
|
|
return new BlockState(GenerateBlock(block));
|
|
}
|
|
|
|
public Chunk GetChunkFromWorldPosition(in Vector3 pos) {
|
|
Vector2Int chunkPos = WorldData.GetChunkPositionFromWorldPosition(pos);
|
|
return _chunks[chunkPos.x, chunkPos.y];
|
|
}
|
|
|
|
private void GenerateWorld() {
|
|
for (int x = (WorldData.WorldSizeInChunks / 2) - Settings.viewDistance;
|
|
x < (WorldData.WorldSizeInChunks / 2) + Settings.viewDistance;
|
|
x++) {
|
|
for (int z = (WorldData.WorldSizeInChunks / 2) - Settings.viewDistance;
|
|
z < (WorldData.WorldSizeInChunks / 2) + Settings.viewDistance;
|
|
z++) {
|
|
Vector2Int currentChunk = new Vector2Int(x, z);
|
|
_chunks[x, z] = new Chunk(this, currentChunk);
|
|
_chunksToCreate.Add(currentChunk);
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < ChunksToUpdate.Count; i++) {
|
|
ChunksToUpdate[0].Update();
|
|
ChunksToUpdate.RemoveAt(0);
|
|
}
|
|
|
|
player.position = spawn;
|
|
}
|
|
|
|
public byte GenerateBlock(in Vector3 block) {
|
|
int yPos = Mathf.FloorToInt(block.y);
|
|
Vector2 noisePosition = new Vector2(block.x, block.z);
|
|
|
|
if (!IsBlockInWorld(block))
|
|
return BlockType.Air;
|
|
|
|
if (yPos == 0)
|
|
return BlockType.Bedrock;
|
|
|
|
#region BiomePass
|
|
int solidGroundHeight = 42;
|
|
float sumOfHeights = 0;
|
|
int count = 0;
|
|
float strongestWeight = 0;
|
|
int strongestBiomeIndex = 0;
|
|
|
|
for (int i = 0; i < biomes.Length; i++) {
|
|
float weight = Noise.Get2dPerlin(noisePosition, biomes[i].offset, biomes[i].scale);
|
|
if (weight > strongestWeight) {
|
|
strongestWeight = weight;
|
|
strongestBiomeIndex = i;
|
|
}
|
|
|
|
float height = biomes[i].terrainHeight * Noise.Get2dPerlin(noisePosition, 0, biomes[i].terrainScale) * weight;
|
|
if (height > 0) {
|
|
sumOfHeights += height;
|
|
count++;
|
|
}
|
|
}
|
|
|
|
BiomeAttributes biome = biomes[strongestBiomeIndex];
|
|
BlockBiomes[(int)block.x, (int)block.z] = strongestBiomeIndex;
|
|
sumOfHeights /= count;
|
|
int terrainHeight = Mathf.FloorToInt(sumOfHeights + solidGroundHeight);
|
|
#endregion
|
|
|
|
#region BasicTerrainPass
|
|
byte blockValue = 0;
|
|
|
|
if (yPos > terrainHeight)
|
|
return BlockType.Air;
|
|
if (yPos == terrainHeight)
|
|
blockValue = biome.surfaceBlock;
|
|
if (yPos < terrainHeight && yPos > terrainHeight - 4)
|
|
blockValue = biome.subSurfaceBlock;
|
|
if (yPos < terrainHeight)
|
|
blockValue = BlockType.Stone;
|
|
#endregion
|
|
|
|
#region SecondTerrainPass
|
|
if (blockValue == BlockType.Stone) {
|
|
foreach (var lode in biome.lodes) {
|
|
if (yPos < lode.minHeight || yPos > lode.maxHeight) continue;
|
|
if (Noise.Get3dPerlin(block, lode.noiseOffset, lode.scale, lode.threshold))
|
|
blockValue = lode.blockId;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region FloraPass
|
|
if (yPos == terrainHeight && biome.placeMajorFlora) {
|
|
if (Noise.Get2dPerlin(noisePosition, 0, biome.majorFloraZoneScale) > biome.majorFloraZoneThreshold) {
|
|
if (Noise.Get2dPerlin(noisePosition, 0, biome.majorFloraPlacementScale) > biome.majorFloraPlacementThreshold) {
|
|
_modificationQueues.Enqueue(Structure.GenerateMajorFlora(biome.majorFloraIndex, block, biome.minHeight, biome.maxHeight));
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
return blockValue;
|
|
}
|
|
|
|
private void CreateChunk() {
|
|
Vector2Int chunk = _chunksToCreate[0];
|
|
_chunksToCreate.RemoveAt(0);
|
|
_chunks[chunk.x, chunk.y].Generate();
|
|
}
|
|
|
|
private void UpdateChunks() {
|
|
bool updated = false;
|
|
int index = 0;
|
|
|
|
lock (ChunkUpdateThreadLock) {
|
|
while (!updated && index < ChunksToUpdate.Count) {
|
|
if (ChunksToUpdate[index].Editable) {
|
|
ChunksToUpdate[index].Update();
|
|
if (!_activeChunks.Contains(ChunksToUpdate[index].Position))
|
|
_activeChunks.Add(ChunksToUpdate[index].Position);
|
|
ChunksToUpdate.RemoveAt(index);
|
|
updated = true;
|
|
}
|
|
else index++;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ThreadedUpdate() {
|
|
while (true) {
|
|
if (!_applyingModifications)
|
|
ApplyModifications();
|
|
if (ChunksToUpdate.Count > 0)
|
|
UpdateChunks();
|
|
}
|
|
}
|
|
|
|
private void OnDisable() {
|
|
if (Settings.enableThreading) {
|
|
_chunkUpdateThread.Abort();
|
|
}
|
|
}
|
|
|
|
private void ApplyModifications() {
|
|
_applyingModifications = true;
|
|
|
|
while (_modificationQueues.Count > 0) {
|
|
Queue<BlockMod> modifications = _modificationQueues.Dequeue();
|
|
|
|
while (modifications.Count > 0) {
|
|
BlockMod mod = modifications.Dequeue();
|
|
|
|
Vector2Int pos = WorldData.GetChunkPositionFromWorldPosition(mod.Position);
|
|
Chunk chunk = _chunks[pos.x, pos.y];
|
|
|
|
if (chunk == null) {
|
|
chunk = new Chunk(this, pos);
|
|
_chunks[pos.x, pos.y] = chunk;
|
|
_chunksToCreate.Add(pos);
|
|
continue;
|
|
}
|
|
|
|
chunk.Modifications.Enqueue(mod);
|
|
}
|
|
}
|
|
|
|
_applyingModifications = false;
|
|
}
|
|
|
|
private void CheckViewDistance(in bool forceUpdate = false) {
|
|
Vector2Int playerChunk = WorldData.GetChunkPositionFromWorldPosition(player.position);
|
|
if (playerChunk == lastPlayerChunk && !forceUpdate) return;
|
|
clouds.UpdateClouds();
|
|
lastPlayerChunk = playerChunk;
|
|
|
|
List<Vector2Int> prevActiveCunks = new List<Vector2Int>(_activeChunks);
|
|
_activeChunks.Clear();
|
|
|
|
for (int x = playerChunk.x - Settings.viewDistance; x < playerChunk.x + Settings.viewDistance; x++) {
|
|
for (int z = playerChunk.y - Settings.viewDistance; z < playerChunk.y + Settings.viewDistance; z++) {
|
|
Vector2Int currentPos = new Vector2Int(x, z);
|
|
prevActiveCunks.Remove(currentPos);
|
|
|
|
if (!IsChunkInWorld(currentPos)) continue;
|
|
Chunk currentChunk = _chunks[x, z];
|
|
|
|
if (currentChunk == null) {
|
|
_chunks[x, z] = new Chunk(this, currentPos);
|
|
_chunksToCreate.Add(currentPos);
|
|
}
|
|
else if (!currentChunk.Active) {
|
|
currentChunk.Active = true;
|
|
}
|
|
|
|
_activeChunks.Add(currentPos);
|
|
}
|
|
}
|
|
|
|
prevActiveCunks.ForEach(chunk => { _chunks[chunk.x, chunk.y].Active = false; });
|
|
}
|
|
|
|
private bool IsChunkInWorld(in Vector2Int chunk) {
|
|
return (chunk.x > 0 && chunk.x < WorldData.WorldSizeInChunks - 1 &&
|
|
chunk.y > 0 && chunk.y < WorldData.WorldSizeInChunks - 1);
|
|
}
|
|
|
|
private bool IsBlockInWorld(in Vector3 block) {
|
|
return (block.x > 0 && block.x <= WorldData.WorldSizeInBlocks &&
|
|
block.y > 0 && block.y < WorldData.ChunkSize.y &&
|
|
block.z > 0 && block.z <= WorldData.WorldSizeInBlocks);
|
|
}
|
|
}
|
|
}
|