turn the ChunkID type alias into a real type

This commit is contained in:
2024-09-08 03:47:26 +10:00
parent 76b61c49ae
commit b804030594
9 changed files with 78 additions and 39 deletions

View File

@ -64,6 +64,7 @@ add_executable(Voxelotl MACOSX_BUNDLE
ChunkMeshGeneration.swift ChunkMeshGeneration.swift
CubeMeshBuilder.swift CubeMeshBuilder.swift
ChunkMeshBuilder.swift ChunkMeshBuilder.swift
ChunkID.swift
World.swift World.swift
Raycast.swift Raycast.swift
Camera.swift Camera.swift

View File

@ -2,8 +2,8 @@ import Foundation
public struct ChunkGeneration { public struct ChunkGeneration {
private let queue: OperationQueue private let queue: OperationQueue
private let localReadyChunks = ConcurrentDictionary<SIMD3<Int>, Chunk>() private let localReadyChunks = ConcurrentDictionary<ChunkID, Chunk>()
private var generatingChunkSet = Set<SIMD3<Int>>() private var generatingChunkSet = Set<ChunkID>()
weak var world: World? weak var world: World?
@ -21,18 +21,18 @@ public struct ChunkGeneration {
self.generatingChunkSet.removeAll() self.generatingChunkSet.removeAll()
} }
public mutating func generate(chunkID: SIMD3<Int>) { public mutating func generate(chunkID: ChunkID) {
if generatingChunkSet.insert(chunkID).inserted { if generatingChunkSet.insert(chunkID).inserted {
self.queueGenerateJob(chunkID: chunkID) self.queueGenerateJob(chunkID: chunkID)
} }
} }
func queueGenerateJob(chunkID: SIMD3<Int>) { func queueGenerateJob(chunkID: ChunkID) {
self.queue.addOperation { self.queue.addOperation {
guard let world = self.world else { guard let world = self.world else {
return return
} }
let chunk = world.generateSingleChunkUncommitted(chunkID: chunkID) let chunk = world.generateSingleChunkUncommitted(id: chunkID)
self.localReadyChunks[chunkID] = chunk self.localReadyChunks[chunkID] = chunk
} }
} }
@ -41,12 +41,12 @@ public struct ChunkGeneration {
guard let world = self.world else { guard let world = self.world else {
return return
} }
let centerChunkID = World.makeID(position: position) let centerChunkID = ChunkID(fromPosition: position)
let range = -2...2 let range = -2...2
for z in range { for z in range {
for y in range { for y in range {
for x 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 { if world.getChunk(id: chunkID) == nil {
self.generate(chunkID: chunkID) self.generate(chunkID: chunkID)
} }
@ -69,7 +69,7 @@ public struct ChunkGeneration {
} }
for (chunkID, chunk) in self.localReadyChunks.take() { for (chunkID, chunk) in self.localReadyChunks.take() {
world.addChunk(chunkID: chunkID, chunk: chunk) world.addChunk(id: chunkID, chunk: chunk)
self.generatingChunkSet.remove(chunkID) self.generatingChunkSet.remove(chunkID)
} }
} }

View File

@ -0,0 +1,44 @@
import simd
public struct ChunkID: Hashable {
public let id: SIMD3<Int>
init(id: SIMD3<Int>) {
self.id = id
}
}
public extension ChunkID {
@inline(__always) init(_ x: Int, _ y: Int, _ z: Int) { self.id = SIMD3(x, y, z) }
@inline(__always) init<F: BinaryFloatingPoint>(fromPosition position: SIMD3<F>) {
self.init(fromPosition: SIMD3(Int(floor(position.x)), Int(floor(position.y)), Int(floor(position.z))))
}
@inline(__always) init(fromPosition position: SIMD3<Int>) { self.id = position &>> Chunk.shift }
@inline(__always) func getPosition() -> SIMD3<Int> { self.id &<< Chunk.shift }
@inline(__always) func getPosition(offset: SIMD3<Int>) -> SIMD3<Int> { self.id &<< Chunk.shift &+ offset }
@inline(__always) func getFloatPosition() -> SIMD3<Float> { SIMD3<Float>(self.id) * Float(Chunk.size) }
@inline(__always) func getFloatPosition(offset: SIMD3<Float>) -> SIMD3<Float> {
SIMD3<Float>(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<Float>(self.id), SIMD3<Float>(other.id))
}
}
public extension ChunkID {
static var axes: [Self] {
[ .X, .Y, .Z ].map { (j: SIMD3<Int>) -> Self in .init(id: j) }
}
}
public extension SIMD3 where Scalar == Int {
init(_ chunkID: ChunkID) { self = chunkID.id }
}

View File

@ -2,7 +2,7 @@ import Foundation
public struct ChunkMeshGeneration { public struct ChunkMeshGeneration {
private let queue: OperationQueue private let queue: OperationQueue
private let localReadyMeshes = ConcurrentDictionary<SIMD3<Int>, RendererMesh?>() private let localReadyMeshes = ConcurrentDictionary<ChunkID, RendererMesh?>()
weak var game: Game? weak var game: Game?
weak var renderer: Renderer? weak var renderer: Renderer?
@ -14,11 +14,11 @@ public struct ChunkMeshGeneration {
self.queue.qualityOfService = .userInitiated self.queue.qualityOfService = .userInitiated
} }
public mutating func generate(chunkID: SIMD3<Int>, chunk: Chunk) { public mutating func generate(id chunkID: ChunkID, chunk: Chunk) {
self.queueGenerateJob(chunkID: chunkID, chunk: chunk) self.queueGenerateJob(id: chunkID, chunk: chunk)
} }
func queueGenerateJob(chunkID: SIMD3<Int>, chunk: Chunk) { func queueGenerateJob(id chunkID: ChunkID, chunk: Chunk) {
self.queue.addOperation { self.queue.addOperation {
guard let game = self.game else { guard let game = self.game else {
return return

View File

@ -7,7 +7,7 @@ class Game: GameDelegate {
var projection: matrix_float4x4 = .identity var projection: matrix_float4x4 = .identity
var world = World(generator: StandardWorldGenerator()) var world = World(generator: StandardWorldGenerator())
var cubeMesh: RendererMesh? var cubeMesh: RendererMesh?
var renderChunks = [SIMD3<Int>: RendererMesh?]() var renderChunks = [ChunkID: RendererMesh?]()
var chunkMeshGeneration: ChunkMeshGeneration! var chunkMeshGeneration: ChunkMeshGeneration!
var modelBatch: ModelBatch! var modelBatch: ModelBatch!
@ -77,9 +77,9 @@ class Game: GameDelegate {
// Regenerate current chunk // Regenerate current chunk
if regenChunk { if regenChunk {
let chunkID = World.makeID(position: self.player.position) let chunkID = ChunkID(fromPosition: self.player.position)
let chunk = self.world.generateSingleChunkUncommitted(chunkID: chunkID) let chunk = self.world.generateSingleChunkUncommitted(id: chunkID)
self.world.addChunk(chunkID: chunkID, chunk: chunk) self.world.addChunk(id: chunkID, chunk: chunk)
} }
self.world.generateAdjacentChunksIfNeeded(position: self.player.position) self.world.generateAdjacentChunksIfNeeded(position: self.player.position)
@ -101,7 +101,7 @@ class Game: GameDelegate {
// Update chunk meshes if needed // Update chunk meshes if needed
self.world.handleRenderDamagedChunks { id, chunk in self.world.handleRenderDamagedChunks { id, chunk in
self.chunkMeshGeneration.generate(chunkID: id, chunk: chunk) self.chunkMeshGeneration.generate(id: id, chunk: chunk)
} }
self.chunkMeshGeneration.acceptReadyMeshes() self.chunkMeshGeneration.acceptReadyMeshes()
@ -111,7 +111,7 @@ class Game: GameDelegate {
if chunk == nil { if chunk == nil {
continue continue
} }
let drawPos = SIMD3<Float>(id &<< Chunk.shift) let drawPos = id.getFloatPosition()
self.modelBatch.draw(.init(mesh: chunk!, material: Self.material), position: drawPos) self.modelBatch.draw(.init(mesh: chunk!, material: Self.material), position: drawPos)
} }

View File

@ -15,7 +15,7 @@ struct StandardWorldGenerator: WorldGenerator {
self.colorNoise = .init(random: &random, octaves: 15, frequency: 0.006667, amplitude: 3) self.colorNoise = .init(random: &random, octaves: 15, frequency: 0.006667, amplitude: 3)
} }
public func makeChunk(id chunkID: SIMD3<Int>) -> Chunk { public func makeChunk(id chunkID: ChunkID) -> Chunk {
let blockFunc = { (height: Float, position: SIMD3<Float>) -> BlockType in let blockFunc = { (height: Float, position: SIMD3<Float>) -> BlockType in
#if true #if true
let value = height + self.terrainNoise.get(position * SIMD3(1, 2, 1)) 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) saturation: 0.47, value: 0.9).linear)
} }
let chunkOrigin = chunkID &<< Chunk.shift let chunkOrigin = chunkID.getPosition()
var chunk = Chunk(position: chunkOrigin) var chunk = Chunk(position: chunkOrigin)
for z in 0..<Chunk.size { for z in 0..<Chunk.size {
for x in 0..<Chunk.size { for x in 0..<Chunk.size {

View File

@ -10,8 +10,8 @@ struct TerrorTowerGenerator: WorldGenerator {
self.noise2 = LayeredNoise(random: &random, octaves: 3, frequency: 0.1) self.noise2 = LayeredNoise(random: &random, octaves: 3, frequency: 0.1)
} }
public func makeChunk(id chunkID: SIMD3<Int>) -> Chunk { public func makeChunk(id chunkID: ChunkID) -> Chunk {
let chunkOrigin = chunkID &<< Chunk.shift let chunkOrigin = chunkID.getPosition()
var chunk = Chunk(position: chunkOrigin) var chunk = Chunk(position: chunkOrigin)
chunk.fill(allBy: { position in chunk.fill(allBy: { position in
let fpos = SIMD3<Float>(chunkOrigin &+ position) let fpos = SIMD3<Float>(chunkOrigin &+ position)

View File

@ -1,6 +1,6 @@
public protocol WorldGenerator { public protocol WorldGenerator {
mutating func reset(seed: UInt64) mutating func reset(seed: UInt64)
func makeChunk(id: SIMD3<Int>) -> Chunk func makeChunk(id: ChunkID) -> Chunk
} }
internal extension RandomProvider where Output == UInt64, Self: RandomSeedable, SeedType == UInt64 { internal extension RandomProvider where Output == UInt64, Self: RandomSeedable, SeedType == UInt64 {

View File

@ -1,12 +1,6 @@
import Foundation import Foundation
public class World { public class World {
public typealias ChunkID = SIMD3<Int>
@inline(__always) public static func makeID<F: BinaryFloatingPoint>(position: SIMD3<F>) -> ChunkID {
makeID(position: SIMD3(Int(floor(position.x)), Int(floor(position.y)), Int(floor(position.z))))
}
@inline(__always) public static func makeID(position: SIMD3<Int>) -> ChunkID { position &>> Chunk.shift }
private var _chunks: Dictionary<ChunkID, Chunk> private var _chunks: Dictionary<ChunkID, Chunk>
private var _chunkDamage: Set<ChunkID> private var _chunkDamage: Set<ChunkID>
private var _generator: any WorldGenerator private var _generator: any WorldGenerator
@ -21,24 +15,24 @@ public class World {
} }
func getBlock(at position: SIMD3<Int>) -> Block { func getBlock(at position: SIMD3<Int>) -> Block {
return if let chunk = self._chunks[position &>> Chunk.shift] { if let chunk = self._chunks[ChunkID(fromPosition: position)] {
chunk.getBlock(at: position) chunk.getBlock(at: position)
} else { Block(.air) } } else { Block(.air) }
} }
func setBlock(at position: SIMD3<Int>, type: BlockType) { func setBlock(at position: SIMD3<Int>, type: BlockType) {
// Find the chunk containing the block position // 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) { if let idx = self._chunks.index(forKey: chunkID) {
// Set the block and mark the containing chunk for render update // Set the block and mark the containing chunk for render update
self._chunks.values[idx].setBlock(at: position, type: type) self._chunks.values[idx].setBlock(at: position, type: type)
self._chunkDamage.insert(chunkID) self._chunkDamage.insert(chunkID)
// Mark adjacent chunks for render update when placing along the chunk border // 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<Int>.X, .Y, .Z ]) { for (i, ofs) in zip(internalPos.indices, [ SIMD3<Int>.X, .Y, .Z ]) {
if internalPos[i] == 0 { if internalPos[i] == 0 {
let id = chunkID &- ofs let id = chunkID &- ChunkID(id: ofs)
if let other = self._chunks[id], if let other = self._chunks[id],
// optim: Damage adjacent chunk only if block is touching a solid // optim: Damage adjacent chunk only if block is touching a solid
case .solid = other.getBlock(internal: (internalPos &- ofs) & Chunk.mask).type case .solid = other.getBlock(internal: (internalPos &- ofs) & Chunk.mask).type
@ -46,7 +40,7 @@ public class World {
self._chunkDamage.insert(id) self._chunkDamage.insert(id)
} }
} else if internalPos[i] == Chunk.size - 1 { } else if internalPos[i] == Chunk.size - 1 {
let id = chunkID &+ ofs let id = chunkID &+ ChunkID(id: ofs)
if let other = self._chunks[id], if let other = self._chunks[id],
// optim: Damage adjacent chunk only if block is touching a solid // optim: Damage adjacent chunk only if block is touching a solid
case .solid = other.getBlock(internal: (internalPos &+ ofs) & Chunk.mask).type case .solid = other.getBlock(internal: (internalPos &+ ofs) & Chunk.mask).type
@ -62,7 +56,7 @@ public class World {
self._chunks[chunkID] self._chunks[chunkID]
} }
public func forEachChunk(_ body: @escaping (_ id: SIMD3<Int>, _ chunk: Chunk) throws -> Void) rethrows { public func forEachChunk(_ body: @escaping (_ id: ChunkID, _ chunk: Chunk) throws -> Void) rethrows {
for i in self._chunks { for i in self._chunks {
try body(i.key, i.value) try body(i.key, i.value)
} }
@ -80,14 +74,14 @@ public class World {
for z in 0..<depth { for z in 0..<depth {
for y in 0..<height { for y in 0..<height {
for x in 0..<width { for x in 0..<width {
let chunkID = SIMD3(x, y, z) &- orig let chunkID = ChunkID(id: SIMD3(x, y, z) &- orig)
self._chunkGeneration.generate(chunkID: chunkID) self._chunkGeneration.generate(chunkID: chunkID)
} }
} }
} }
} }
func generateSingleChunkUncommitted(chunkID: SIMD3<Int>) -> Chunk { func generateSingleChunkUncommitted(id chunkID: ChunkID) -> Chunk {
self._generator.makeChunk(id: chunkID) self._generator.makeChunk(id: chunkID)
} }
@ -95,10 +89,10 @@ public class World {
self._chunkGeneration.generateAdjacentIfNeeded(position: position) 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._chunks[chunkID] = chunk
self._chunkDamage.insert(chunkID) self._chunkDamage.insert(chunkID)
for i: ChunkID in [ .X, .Y, .Z ] { for i in ChunkID.axes {
for otherID in [ chunkID &- i, chunkID &+ i ] { for otherID in [ chunkID &- i, chunkID &+ i ] {
if self._chunks.keys.contains(otherID) { if self._chunks.keys.contains(otherID) {
self._chunkDamage.insert(otherID) self._chunkDamage.insert(otherID)