diff --git a/Sources/Voxelotl/Chunk.swift b/Sources/Voxelotl/Chunk.swift index 33e9e90..382035d 100644 --- a/Sources/Voxelotl/Chunk.swift +++ b/Sources/Voxelotl/Chunk.swift @@ -1,3 +1,5 @@ +import simd + public struct Chunk { public static let shift = 4 // 16 public static let size: Int = 1 << shift @@ -10,7 +12,11 @@ public struct Chunk { public let origin: SIMD3 private var blocks: [Block] - + + static func getID(position: SIMD3) -> SIMD3 { + SIMD3(floor(position)) &>> Chunk.shift + } + init(position: SIMD3, blocks: [Block]) { assert(blocks.count == Self.blockCount) self.origin = position diff --git a/Sources/Voxelotl/Game.swift b/Sources/Voxelotl/Game.swift index 8671526..ce5da63 100644 --- a/Sources/Voxelotl/Game.swift +++ b/Sources/Voxelotl/Game.swift @@ -78,6 +78,13 @@ class Game: GameDelegate { } self.player.update(deltaTime: deltaTime, world: world, camera: &camera) + let centerChunkID = Chunk.getID(position: self.player.position) + chunkGenerateNeighbors.forEach { offset in + let chunkID = centerChunkID &+ offset + if self.world.getChunk(id: chunkID) == nil { + self.world.generate(chunkID: chunkID) + } + } } func draw(_ renderer: Renderer, _ time: GameTime) { @@ -112,4 +119,34 @@ class Game: GameDelegate { func resize(_ size: Size) { self.camera.size = size } + + private let chunkGenerateNeighbors: [SIMD3] = [ + SIMD3(-1, -1, -1), + SIMD3(0, -1, -1), + SIMD3(1, -1, -1), + SIMD3(-1, 0, -1), + SIMD3(0, 0, -1), + SIMD3(1, 0, -1), + SIMD3(-1, 1, -1), + SIMD3(0, 1, -1), + SIMD3(1, 1, -1), + SIMD3(-1, -1, 0), + SIMD3(0, -1, 0), + SIMD3(1, -1, 0), + SIMD3(-1, 0, 0), + SIMD3(0, 0, 0), + SIMD3(1, 0, 0), + SIMD3(-1, 1, 0), + SIMD3(0, 1, 0), + SIMD3(1, 1, 0), + SIMD3(-1, -1, 1), + SIMD3(0, -1, 1), + SIMD3(1, -1, 1), + SIMD3(-1, 0, 1), + SIMD3(0, 0, 1), + SIMD3(1, 0, 1), + SIMD3(-1, 1, 1), + SIMD3(0, 1, 1), + SIMD3(1, 1, 1), + ] } diff --git a/Sources/Voxelotl/World.swift b/Sources/Voxelotl/World.swift index 7bff672..6a14a3d 100644 --- a/Sources/Voxelotl/World.swift +++ b/Sources/Voxelotl/World.swift @@ -4,6 +4,8 @@ public class World { private var _chunks: Dictionary, Chunk> private var _generator: WorldGenerator + let _chunkLock: NSLock = .init() + public init() { self._chunks = [:] self._generator = WorldGenerator() @@ -18,6 +20,13 @@ public class World { func setBlock(at position: SIMD3, type: BlockType) { self._chunks[position &>> Chunk.shift]?.setBlock(at: position, type: type) } + + func getChunk(id chunkID: SIMD3) -> Chunk? { + self._chunkLock.lock() + let chunk = self._chunks[chunkID] + self._chunkLock.unlock() + return chunk + } func generate(width: Int, height: Int, depth: Int, seed: UInt64) { self._generator.reset(seed: seed) @@ -26,18 +35,27 @@ public class World { for y in 0..) { - self._chunks[chunkID] = self._generator.makeChunk(id: chunkID) + if self._generator.isCurrentlyGenerating(id: chunkID) { return } + self._generator.makeChunk(id: chunkID) { chunk in + self._chunkLock.lock() + self._chunks[chunkID] = chunk + self._chunkLock.unlock() + } } var instances: [Instance] { - self._chunks.values.flatMap { chunk in + self._chunkLock.lock() + defer { + self._chunkLock.unlock() + } + return self._chunks.values.flatMap { chunk in chunk.compactMap { block, position in if case let .solid(color) = block.type { Instance( diff --git a/Sources/Voxelotl/WorldGenerator.swift b/Sources/Voxelotl/WorldGenerator.swift index ea1c056..2474848 100644 --- a/Sources/Voxelotl/WorldGenerator.swift +++ b/Sources/Voxelotl/WorldGenerator.swift @@ -1,7 +1,11 @@ -struct WorldGenerator { - var noise: ImprovedPerlin!, noise2: SimplexNoise! +import Foundation - public mutating func reset(seed: UInt64) { +class WorldGenerator: NSObject { + var noise: ImprovedPerlin!, noise2: SimplexNoise! + var generating: Set> = .init() + let generatingLock: NSLock = .init() + + public func reset(seed: UInt64) { var random: any RandomProvider let initialState = SplitMix64.createState(seed: seed) #if true @@ -14,26 +18,49 @@ struct WorldGenerator { self.noise2 = SimplexNoise(random: &random) } - public func makeChunk(id chunkID: SIMD3) -> Chunk { - let chunkOrigin = chunkID &<< Chunk.shift - var chunk = Chunk(position: chunkOrigin) - chunk.fill(allBy: { position in - let fpos = SIMD3(position) - let threshold: Float = 0.6 - let value = fpos.y / Float(Chunk.size) - + self.noise.get(fpos * 0.05) * 1.1 - + self.noise.get(fpos * 0.10) * 0.5 - + self.noise.get(fpos * 0.30) * 0.23 - return if value < threshold { - .solid(.init( - hue: Float16(180 + self.noise2.get(fpos * 0.05) * 180), - saturation: Float16(0.5 + self.noise2.get(SIMD4(fpos * 0.05, 4)) * 0.5), - value: Float16(0.5 + self.noise2.get(SIMD4(fpos * 0.05, 9)) * 0.5).lerp(0.5, 1)).linear) - } else { - .air + public func isCurrentlyGenerating(id chunkID: SIMD3) -> Bool { + self.generatingLock.lock() + defer { + self.generatingLock.unlock() + } + return self.generating.contains(chunkID) + } + + public func makeChunk(id chunkID: SIMD3, completion: @escaping (Chunk) -> Void) { + self.generatingLock.lock() + print(self.generating) + if !self.generating.insert(chunkID).inserted { + self.generatingLock.unlock() + return + } + self.generatingLock.unlock() + DispatchQueue.global(qos: .userInteractive).async { + defer { + self.generatingLock.lock() + self.generating.remove(chunkID) + self.generatingLock.unlock() } - }) - return chunk + + let chunkOrigin = chunkID &<< Chunk.shift + var chunk = Chunk(position: chunkOrigin) + chunk.fill(allBy: { position in + let fpos = SIMD3(position) + let threshold: Float = 0.6 + let value = fpos.y / Float(Chunk.size) + + self.noise.get(fpos * 0.05) * 1.1 + + self.noise.get(fpos * 0.10) * 0.5 + + self.noise.get(fpos * 0.30) * 0.23 + return if value < threshold { + .solid(.init( + hue: Float16(180 + self.noise2.get(fpos * 0.05) * 180), + saturation: Float16(0.5 + self.noise2.get(SIMD4(fpos * 0.05, 4)) * 0.5), + value: Float16(0.5 + self.noise2.get(SIMD4(fpos * 0.05, 9)) * 0.5).lerp(0.5, 1)).linear) + } else { + .air + } + }) + completion(chunk) + } } }