diff --git a/Sources/Voxelotl/CMakeLists.txt b/Sources/Voxelotl/CMakeLists.txt index daeafa8..a174b68 100644 --- a/Sources/Voxelotl/CMakeLists.txt +++ b/Sources/Voxelotl/CMakeLists.txt @@ -64,6 +64,7 @@ add_executable(Voxelotl MACOSX_BUNDLE ChunkMeshGeneration.swift CubeMeshBuilder.swift ChunkMeshBuilder.swift + ChunkID.swift World.swift Raycast.swift Camera.swift diff --git a/Sources/Voxelotl/ChunkGeneration.swift b/Sources/Voxelotl/ChunkGeneration.swift index aa9267a..7a9569e 100644 --- a/Sources/Voxelotl/ChunkGeneration.swift +++ b/Sources/Voxelotl/ChunkGeneration.swift @@ -2,8 +2,8 @@ import Foundation public struct ChunkGeneration { private let queue: OperationQueue - private let localReadyChunks = ConcurrentDictionary, Chunk>() - private var generatingChunkSet = Set>() + private let localReadyChunks = ConcurrentDictionary() + private var generatingChunkSet = Set() weak var world: World? @@ -21,18 +21,18 @@ public struct ChunkGeneration { self.generatingChunkSet.removeAll() } - public mutating func generate(chunkID: SIMD3) { + public mutating func generate(chunkID: ChunkID) { if generatingChunkSet.insert(chunkID).inserted { self.queueGenerateJob(chunkID: chunkID) } } - func queueGenerateJob(chunkID: SIMD3) { + func queueGenerateJob(chunkID: ChunkID) { self.queue.addOperation { guard let world = self.world else { return } - let chunk = world.generateSingleChunkUncommitted(chunkID: chunkID) + let chunk = world.generateSingleChunkUncommitted(id: chunkID) self.localReadyChunks[chunkID] = chunk } } @@ -41,12 +41,12 @@ public struct ChunkGeneration { guard let world = self.world else { return } - let centerChunkID = World.makeID(position: position) + let centerChunkID = ChunkID(fromPosition: position) let range = -2...2 for z in range { for y in range { for x in range { - let chunkID = centerChunkID &+ SIMD3(x, y, z) + let chunkID = centerChunkID &+ ChunkID(x, y, z) if world.getChunk(id: chunkID) == nil { self.generate(chunkID: chunkID) } @@ -69,7 +69,7 @@ public struct ChunkGeneration { } for (chunkID, chunk) in self.localReadyChunks.take() { - world.addChunk(chunkID: chunkID, chunk: chunk) + world.addChunk(id: chunkID, chunk: chunk) self.generatingChunkSet.remove(chunkID) } } diff --git a/Sources/Voxelotl/ChunkID.swift b/Sources/Voxelotl/ChunkID.swift new file mode 100644 index 0000000..9e69a4f --- /dev/null +++ b/Sources/Voxelotl/ChunkID.swift @@ -0,0 +1,44 @@ +import simd + +public struct ChunkID: Hashable { + public let id: SIMD3 + + init(id: SIMD3) { + self.id = id + } +} + +public extension ChunkID { + @inline(__always) init(_ x: Int, _ y: Int, _ z: Int) { self.id = SIMD3(x, y, z) } + + @inline(__always) init(fromPosition position: SIMD3) { + self.init(fromPosition: SIMD3(Int(floor(position.x)), Int(floor(position.y)), Int(floor(position.z)))) + } + @inline(__always) init(fromPosition position: SIMD3) { self.id = position &>> Chunk.shift } + + @inline(__always) func getPosition() -> SIMD3 { self.id &<< Chunk.shift } + @inline(__always) func getPosition(offset: SIMD3) -> SIMD3 { self.id &<< Chunk.shift &+ offset } + @inline(__always) func getFloatPosition() -> SIMD3 { SIMD3(self.id) * Float(Chunk.size) } + @inline(__always) func getFloatPosition(offset: SIMD3) -> SIMD3 { + SIMD3(self.id) * Float(Chunk.size) + offset + } + + @inline(__always) static func &+ (lhs: Self, rhs: Self) -> Self { .init(id: lhs.id &+ rhs.id) } + @inline(__always) static func &- (lhs: Self, rhs: Self) -> Self { .init(id: lhs.id &- rhs.id) } +} + +public extension ChunkID { + @inlinable func distance(_ other: Self) -> Float { + simd_distance(SIMD3(self.id), SIMD3(other.id)) + } +} + +public extension ChunkID { + static var axes: [Self] { + [ .X, .Y, .Z ].map { (j: SIMD3) -> Self in .init(id: j) } + } +} + +public extension SIMD3 where Scalar == Int { + init(_ chunkID: ChunkID) { self = chunkID.id } +} diff --git a/Sources/Voxelotl/ChunkMeshGeneration.swift b/Sources/Voxelotl/ChunkMeshGeneration.swift index a58ccfa..aea1cce 100644 --- a/Sources/Voxelotl/ChunkMeshGeneration.swift +++ b/Sources/Voxelotl/ChunkMeshGeneration.swift @@ -2,7 +2,7 @@ import Foundation public struct ChunkMeshGeneration { private let queue: OperationQueue - private let localReadyMeshes = ConcurrentDictionary, RendererMesh?>() + private let localReadyMeshes = ConcurrentDictionary() weak var game: Game? weak var renderer: Renderer? @@ -14,11 +14,11 @@ public struct ChunkMeshGeneration { self.queue.qualityOfService = .userInitiated } - public mutating func generate(chunkID: SIMD3, chunk: Chunk) { - self.queueGenerateJob(chunkID: chunkID, chunk: chunk) + public mutating func generate(id chunkID: ChunkID, chunk: Chunk) { + self.queueGenerateJob(id: chunkID, chunk: chunk) } - func queueGenerateJob(chunkID: SIMD3, chunk: Chunk) { + func queueGenerateJob(id chunkID: ChunkID, chunk: Chunk) { self.queue.addOperation { guard let game = self.game else { return diff --git a/Sources/Voxelotl/Game.swift b/Sources/Voxelotl/Game.swift index 6b56126..f692d2d 100644 --- a/Sources/Voxelotl/Game.swift +++ b/Sources/Voxelotl/Game.swift @@ -7,7 +7,7 @@ class Game: GameDelegate { var projection: matrix_float4x4 = .identity var world = World(generator: StandardWorldGenerator()) var cubeMesh: RendererMesh? - var renderChunks = [SIMD3: RendererMesh?]() + var renderChunks = [ChunkID: RendererMesh?]() var chunkMeshGeneration: ChunkMeshGeneration! var modelBatch: ModelBatch! @@ -77,9 +77,9 @@ class Game: GameDelegate { // Regenerate current chunk if regenChunk { - let chunkID = World.makeID(position: self.player.position) - let chunk = self.world.generateSingleChunkUncommitted(chunkID: chunkID) - self.world.addChunk(chunkID: chunkID, chunk: chunk) + let chunkID = ChunkID(fromPosition: self.player.position) + let chunk = self.world.generateSingleChunkUncommitted(id: chunkID) + self.world.addChunk(id: chunkID, chunk: chunk) } self.world.generateAdjacentChunksIfNeeded(position: self.player.position) @@ -101,7 +101,7 @@ class Game: GameDelegate { // Update chunk meshes if needed self.world.handleRenderDamagedChunks { id, chunk in - self.chunkMeshGeneration.generate(chunkID: id, chunk: chunk) + self.chunkMeshGeneration.generate(id: id, chunk: chunk) } self.chunkMeshGeneration.acceptReadyMeshes() @@ -111,7 +111,7 @@ class Game: GameDelegate { if chunk == nil { continue } - let drawPos = SIMD3(id &<< Chunk.shift) + let drawPos = id.getFloatPosition() self.modelBatch.draw(.init(mesh: chunk!, material: Self.material), position: drawPos) } diff --git a/Sources/Voxelotl/Generator/StandardWorldGenerator.swift b/Sources/Voxelotl/Generator/StandardWorldGenerator.swift index 9702cef..2742f4a 100644 --- a/Sources/Voxelotl/Generator/StandardWorldGenerator.swift +++ b/Sources/Voxelotl/Generator/StandardWorldGenerator.swift @@ -15,7 +15,7 @@ struct StandardWorldGenerator: WorldGenerator { self.colorNoise = .init(random: &random, octaves: 15, frequency: 0.006667, amplitude: 3) } - public func makeChunk(id chunkID: SIMD3) -> Chunk { + public func makeChunk(id chunkID: ChunkID) -> Chunk { let blockFunc = { (height: Float, position: SIMD3) -> BlockType in #if true let value = height + self.terrainNoise.get(position * SIMD3(1, 2, 1)) @@ -37,7 +37,7 @@ struct StandardWorldGenerator: WorldGenerator { saturation: 0.47, value: 0.9).linear) } - let chunkOrigin = chunkID &<< Chunk.shift + let chunkOrigin = chunkID.getPosition() var chunk = Chunk(position: chunkOrigin) for z in 0..) -> Chunk { - let chunkOrigin = chunkID &<< Chunk.shift + public func makeChunk(id chunkID: ChunkID) -> Chunk { + let chunkOrigin = chunkID.getPosition() var chunk = Chunk(position: chunkOrigin) chunk.fill(allBy: { position in let fpos = SIMD3(chunkOrigin &+ position) diff --git a/Sources/Voxelotl/Generator/WorldGenerator.swift b/Sources/Voxelotl/Generator/WorldGenerator.swift index 232f237..70a7e6e 100644 --- a/Sources/Voxelotl/Generator/WorldGenerator.swift +++ b/Sources/Voxelotl/Generator/WorldGenerator.swift @@ -1,6 +1,6 @@ public protocol WorldGenerator { mutating func reset(seed: UInt64) - func makeChunk(id: SIMD3) -> Chunk + func makeChunk(id: ChunkID) -> Chunk } internal extension RandomProvider where Output == UInt64, Self: RandomSeedable, SeedType == UInt64 { diff --git a/Sources/Voxelotl/World.swift b/Sources/Voxelotl/World.swift index 214154c..3594b8b 100644 --- a/Sources/Voxelotl/World.swift +++ b/Sources/Voxelotl/World.swift @@ -1,12 +1,6 @@ import Foundation public class World { - 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: any WorldGenerator @@ -21,24 +15,24 @@ public class World { } func getBlock(at position: SIMD3) -> Block { - return if let chunk = self._chunks[position &>> Chunk.shift] { + if let chunk = self._chunks[ChunkID(fromPosition: position)] { chunk.getBlock(at: position) } else { Block(.air) } } func setBlock(at position: SIMD3, type: BlockType) { // Find the chunk containing the block position - let chunkID = position &>> Chunk.shift + let chunkID = ChunkID(fromPosition: position) 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 + let internalPos = position &- chunkID.getPosition() for (i, ofs) in zip(internalPos.indices, [ SIMD3.X, .Y, .Z ]) { if internalPos[i] == 0 { - let id = chunkID &- ofs + let id = chunkID &- ChunkID(id: ofs) if let other = self._chunks[id], // optim: Damage adjacent chunk only if block is touching a solid case .solid = other.getBlock(internal: (internalPos &- ofs) & Chunk.mask).type @@ -46,7 +40,7 @@ public class World { self._chunkDamage.insert(id) } } else if internalPos[i] == Chunk.size - 1 { - let id = chunkID &+ ofs + let id = chunkID &+ ChunkID(id: ofs) if let other = self._chunks[id], // optim: Damage adjacent chunk only if block is touching a solid case .solid = other.getBlock(internal: (internalPos &+ ofs) & Chunk.mask).type @@ -62,7 +56,7 @@ public class World { self._chunks[chunkID] } - public func forEachChunk(_ body: @escaping (_ id: SIMD3, _ chunk: Chunk) throws -> Void) rethrows { + public func forEachChunk(_ body: @escaping (_ id: ChunkID, _ chunk: Chunk) throws -> Void) rethrows { for i in self._chunks { try body(i.key, i.value) } @@ -80,14 +74,14 @@ public class World { for z in 0..) -> Chunk { + func generateSingleChunkUncommitted(id chunkID: ChunkID) -> Chunk { self._generator.makeChunk(id: chunkID) } @@ -95,10 +89,10 @@ public class World { self._chunkGeneration.generateAdjacentIfNeeded(position: position) } - public func addChunk(chunkID: ChunkID, chunk: Chunk) { + public func addChunk(id chunkID: ChunkID, chunk: Chunk) { self._chunks[chunkID] = chunk self._chunkDamage.insert(chunkID) - for i: ChunkID in [ .X, .Y, .Z ] { + for i in ChunkID.axes { for otherID in [ chunkID &- i, chunkID &+ i ] { if self._chunks.keys.contains(otherID) { self._chunkDamage.insert(otherID)