diff --git a/Sources/Voxelotl/Game.swift b/Sources/Voxelotl/Game.swift index 0e10741..049c038 100644 --- a/Sources/Voxelotl/Game.swift +++ b/Sources/Voxelotl/Game.swift @@ -26,9 +26,6 @@ class Game: GameDelegate { var projection: matrix_float4x4 = .identity var world = World() var cubeMesh: RendererMesh? - - var renderMode: Bool = false - var damageChunks = [SIMD3: Mesh]() var renderChunks = [SIMD3: RendererMesh]() func create(_ renderer: Renderer) { @@ -54,15 +51,6 @@ class Game: GameDelegate { #else self.world.generate(width: 5, height: 3, depth: 5, seed: seed) #endif - - // Build chunk meshes - self.rebuildChunkMeshes() - } - - private func rebuildChunkMeshes() { - self.world.forEachChunk { id, chunk in - self.damageChunks[id] = ChunkMeshBuilder.build(world: self.world, chunkID: id) - } } func fixedUpdate(_ time: GameTime) { @@ -76,30 +64,31 @@ class Game: GameDelegate { let deltaTime = min(Float(time.delta.asFloat), 1.0 / 15) - var reset = false, generate = false, toggleRenderMode = false + var reset = false, generate = false, regenChunk = false if let pad = GameController.current?.state { if pad.pressed(.back) { reset = true } if pad.pressed(.start) { generate = true } - if pad.pressed(.guide) { toggleRenderMode = true } + if pad.pressed(.guide) { regenChunk = true } } if Keyboard.pressed(.r) { reset = true } if Keyboard.pressed(.g) { generate = true } - if Keyboard.pressed(.p, repeat: true) { toggleRenderMode = true } - if Keyboard.pressed(.leftBracket) { self.rebuildChunkMeshes() } + if Keyboard.pressed(.p) { regenChunk = true } // Player reset if reset { self.resetPlayer() } - // Regenerate + // Regenerate world if generate { self.generateWorld() } - if toggleRenderMode { - self.renderMode = !self.renderMode - } self.player.update(deltaTime: deltaTime, world: world, camera: &camera) + + // Regenerate current chunk + if regenChunk { + self.world.generate(chunkID: World.makeID(position: self.player.position)) + } } func draw(_ renderer: Renderer, _ time: GameTime) { @@ -114,32 +103,28 @@ class Game: GameDelegate { specular: Color(rgba8888: 0x2F2F2F00).linear, gloss: 75) - if self.renderMode { - // Update chunk meshes if needed - if !self.damageChunks.isEmpty { - for i in self.damageChunks { - if let new = renderer.createMesh(i.1) { - self.renderChunks[i.0] = new - } else { - self.renderChunks.removeValue(forKey: i.0) - } - } - self.damageChunks = [:] - } - - for (id, chunk) in self.renderChunks { - let drawPos = SIMD3(id &<< Chunk.shift) - renderer.draw( - model: .translate(drawPos), - color: .white, - mesh: chunk, - material: material, - environment: env, - camera: self.camera) + // Update chunk meshes if needed + self.world.handleRenderDamagedChunks { id, chunk in + let mesh = ChunkMeshBuilder.build(world: self.world, chunkID: id) + if let renderMesh = renderer.createMesh(mesh) { + self.renderChunks[id] = renderMesh + } else { + self.renderChunks.removeValue(forKey: id) } } - var instances = self.renderMode ? [Instance]() : world.instances + for (id, chunk) in self.renderChunks { + let drawPos = SIMD3(id &<< Chunk.shift) + renderer.draw( + model: .translate(drawPos), + color: .white, + mesh: chunk, + material: material, + environment: env, + camera: self.camera) + } + + var instances = [Instance]() if let position = player.rayhitPos { instances.append( Instance( diff --git a/Sources/Voxelotl/World.swift b/Sources/Voxelotl/World.swift index 352754a..33984d9 100644 --- a/Sources/Voxelotl/World.swift +++ b/Sources/Voxelotl/World.swift @@ -1,11 +1,19 @@ import Foundation public class World { - private var _chunks: Dictionary, Chunk> + public typealias ChunkID = SIMD3 + @inline(__always) public static func makeID(position: SIMD3) -> ChunkID { + makeID(position: SIMD3(Int(floor(position.x)), Int(floor(position.y)), Int(floor(position.z)))) + } + @inline(__always) public static func makeID(position: SIMD3) -> ChunkID { position &>> Chunk.shift } + + private var _chunks: Dictionary + private var _chunkDamage: Set private var _generator: WorldGenerator public init() { self._chunks = [:] + self._chunkDamage = [] self._generator = WorldGenerator() } @@ -16,10 +24,32 @@ public class World { } func setBlock(at position: SIMD3, type: BlockType) { - self._chunks[position &>> Chunk.shift]?.setBlock(at: position, type: type) + // Find the chunk containing the block position + let chunkID = position &>> Chunk.shift + if let idx = self._chunks.index(forKey: chunkID) { + // Set the block and mark the containing chunk for render update + self._chunks.values[idx].setBlock(at: position, type: type) + self._chunkDamage.insert(chunkID) + + // Mark adjacent chunks for render update when placing along the chunk border + let internalPos = position &- chunkID &<< Chunk.shift + for (i, ofs) in zip(internalPos.indices, [ SIMD3.X, .Y, .Z ]) { + if internalPos[i] == 0 { + let id = chunkID &- ofs + if self._chunks.keys.contains(id) { + self._chunkDamage.insert(id) + } + } else if internalPos[i] == Chunk.size - 1 { + let id = chunkID &+ ofs + if self._chunks.keys.contains(id) { + self._chunkDamage.insert(id) + } + } + } + } } - func getChunk(id chunkID: SIMD3) -> Chunk? { + func getChunk(id chunkID: ChunkID) -> Chunk? { self._chunks[chunkID] } @@ -37,25 +67,28 @@ public class World { for x in 0..) { + func generate(chunkID: ChunkID) { self._chunks[chunkID] = self._generator.makeChunk(id: chunkID) - } - - var instances: [Instance] { - self._chunks.values.flatMap { chunk in - chunk.compactMap { block, position in - if case let .solid(color) = block.type { - Instance( - position: SIMD3(position) + 0.5, - scale: .init(repeating: 0.5), - color: color) - } else { nil } + self._chunkDamage.insert(chunkID) + for i: ChunkID in [ .X, .Y, .Z ] { + for otherID in [ chunkID &- i, chunkID &+ i ] { + if self._chunks.keys.contains(otherID) { + self._chunkDamage.insert(otherID) + } } } } + + func handleRenderDamagedChunks(_ body: (_ id: ChunkID, _ chunk: Chunk) -> Void) { + for id in self._chunkDamage { + body(id, self._chunks[id]!) + } + self._chunkDamage.removeAll(keepingCapacity: true) + } }