From 160c9c8a68f00eb070b1e4abe27956ff12bb1436 Mon Sep 17 00:00:00 2001 From: a dinosaur Date: Sun, 25 Aug 2024 19:23:47 +1000 Subject: [PATCH] multiple chunks --- Sources/Voxelotl/CMakeLists.txt | 2 + Sources/Voxelotl/Chunk.swift | 70 ++++++++++++++++----------------- Sources/Voxelotl/Game.swift | 55 +++++++++----------------- Sources/Voxelotl/Player.swift | 24 +++++------ Sources/Voxelotl/Raycast.swift | 4 +- Sources/Voxelotl/World.swift | 61 ++++++++++++++++++++++++++++ 6 files changed, 128 insertions(+), 88 deletions(-) create mode 100644 Sources/Voxelotl/World.swift diff --git a/Sources/Voxelotl/CMakeLists.txt b/Sources/Voxelotl/CMakeLists.txt index e3bcd7c..b664b7a 100644 --- a/Sources/Voxelotl/CMakeLists.txt +++ b/Sources/Voxelotl/CMakeLists.txt @@ -45,6 +45,7 @@ add_executable(Voxelotl MACOSX_BUNDLE # Game logic classes Chunk.swift + World.swift Raycast.swift Player.swift Game.swift @@ -59,6 +60,7 @@ set_source_files_properties( target_include_directories(Voxelotl PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") target_link_libraries(Voxelotl PRIVATE SDLSwift) +target_compile_definitions(Voxelotl PRIVATE $<$:DEBUG>) set_target_properties(Voxelotl PROPERTIES XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME YES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "gay.pizza.voxelotl" diff --git a/Sources/Voxelotl/Chunk.swift b/Sources/Voxelotl/Chunk.swift index 673b560..33e9e90 100644 --- a/Sources/Voxelotl/Chunk.swift +++ b/Sources/Voxelotl/Chunk.swift @@ -1,21 +1,24 @@ public struct Chunk { - public static let chunkSize: Int = 16 - public static let blockCount = chunkSize * chunkSize * chunkSize + public static let shift = 4 // 16 + public static let size: Int = 1 << shift + public static let mask: Int = size - 1 - private static let yStride = chunkSize - private static let zStride = chunkSize * chunkSize + public static let blockCount = size * size * size + + private static let yStride = size + private static let zStride = size * size - public let position: SIMD3 + public let origin: SIMD3 private var blocks: [Block] init(position: SIMD3, blocks: [Block]) { assert(blocks.count == Self.blockCount) - self.position = position + self.origin = position self.blocks = blocks } init(position: SIMD3) { - self.position = position + self.origin = position self.blocks = Array( repeating: BlockType.air, count: Self.blockCount @@ -23,47 +26,40 @@ public struct Chunk { } func getBlock(at position: SIMD3) -> Block { + getBlock(internal: position &- self.origin) + } + + func getBlock(internal position: SIMD3) -> Block { if position.x < 0 || position.y < 0 || position.z < 0 { Block(.air) - } else if position.x >= Self.chunkSize || position.y >= Self.chunkSize || position.z >= Self.chunkSize { + } else if position.x >= Self.size || position.y >= Self.size || position.z >= Self.size { Block(.air) } else { blocks[position.x + position.y * Self.yStride + position.z * Self.zStride] } } - + mutating func setBlock(at position: SIMD3, type: BlockType) { + setBlock(internal: position &- self.origin, type: type) + } + + mutating func setBlock(internal position: SIMD3, type: BlockType) { if position.x < 0 || position.y < 0 || position.z < 0 { return } - if position.x >= Self.chunkSize || position.y >= Self.chunkSize || position.z >= Self.chunkSize { + if position.x >= Self.size || position.y >= Self.size || position.z >= Self.size { return } blocks[position.x + position.y * Self.yStride + position.z * Self.zStride].type = type } - + mutating func fill(allBy calculation: (_ position: SIMD3) -> BlockType) { - var i = 0 - for x in 0..) -> Void) { - for x in 0..> Self.shift) & Self.mask + let z = (i &>> (Self.shift + Self.shift)) & Self.mask + blocks[i].type = calculation(self.origin &+ SIMD3(x, y, z)) } } @@ -75,12 +71,12 @@ public struct Chunk { var position = SIMD3() for i in self.blocks.indices { - out.append(try transform(blocks[i], position)) + out.append(try transform(blocks[i], self.origin &+ position)) position.x += 1 - if position.x == Self.chunkSize { + if position.x == Self.size { position.x = 0 position.y += 1 - if position.y == Self.chunkSize { + if position.y == Self.size { position.y = 0 position.z += 1 } @@ -98,14 +94,14 @@ public struct Chunk { var position = SIMD3() for i in self.blocks.indices { - if let element = try transform(blocks[i], position) { + if let element = try transform(blocks[i], self.origin &+ position) { out.append(element) } position.x += 1 - if position.x == Self.chunkSize { + if position.x == Self.size { position.x = 0 position.y += 1 - if position.y == Self.chunkSize { + if position.y == Self.size { position.y = 0 position.z += 1 } diff --git a/Sources/Voxelotl/Game.swift b/Sources/Voxelotl/Game.swift index 79be5a8..22f03ac 100644 --- a/Sources/Voxelotl/Game.swift +++ b/Sources/Voxelotl/Game.swift @@ -24,7 +24,7 @@ class Game: GameDelegate { var camera = Camera(fov: 60, size: .one, range: 0.06...900) var player = Player() var projection: matrix_float4x4 = .identity - var chunk = Chunk(position: .zero) + var world = World() func create(_ renderer: Renderer) { self.resetPlayer() @@ -34,7 +34,7 @@ class Game: GameDelegate { } private func resetPlayer() { - self.player.position = .init(repeating: 0.5) + .init(0, Float(Chunk.chunkSize), 0) + self.player.position = .init(repeating: 0.5) + .init(0, Float(Chunk.size), 0) self.player.velocity = .zero self.player.rotation = .init(.pi, 0) } @@ -50,20 +50,11 @@ class Game: GameDelegate { UInt64(Arc4Random.instance.next()) | UInt64(Arc4Random.instance.next()) << 32, UInt64(Arc4Random.instance.next()) | UInt64(Arc4Random.instance.next()) << 32)) #endif - let noise = ImprovedPerlin(random: &random) - self.chunk.fill(allBy: { position in - let fpos = SIMD3(position) - return if fpos.y / Float(Chunk.chunkSize) - + noise.get(fpos * 0.07) * 0.7 - + noise.get(fpos * 0.321 + 100) * 0.3 < 0.6 { - .solid(.init( - r: Float16(noise.get(fpos * 0.1)), - g: Float16(noise.get(fpos * 0.1 + 10)), - b: Float16(noise.get(fpos * 0.1 + 100))).mix(.white, 0.4).linear) - } else { - .air - } - }) +#if DEBUG + self.world.generate(width: 2, height: 1, depth: 1, random: &random) +#else + self.world.generate(width: 5, height: 3, depth: 5, random: &random) +#endif } func fixedUpdate(_ time: GameTime) { @@ -77,27 +68,24 @@ class Game: GameDelegate { let deltaTime = min(Float(time.delta.asFloat), 1.0 / 15) - var destroy = false + var reset = false, generate = false if let pad = GameController.current?.state { - // Player reset - if pad.pressed(.back) { - self.resetPlayer() - } - - // Regenerate - if pad.pressed(.start) { - self.generateWorld() - } + if pad.pressed(.back) { reset = true } + if pad.pressed(.start) { generate = true } } + if Keyboard.pressed(.r) { reset = true } + if Keyboard.pressed(.g) { generate = true } - if Keyboard.pressed(.r) { + // Player reset + if reset { self.resetPlayer() } - if Keyboard.pressed(.g) { + // Regenerate + if generate { self.generateWorld() } - self.player.update(deltaTime: deltaTime, chunk: &chunk) + self.player.update(deltaTime: deltaTime, world: world) self.camera.position = player.eyePosition self.camera.rotation = player.eyeRotation } @@ -114,14 +102,7 @@ class Game: GameDelegate { specular: Color(rgba8888: 0x2F2F2F00).linear, gloss: 75) - var instances = chunk.compactMap { block, position in - if case let .solid(color) = block.type { - Instance( - position: SIMD3(chunk.position &+ position) + 0.5, - scale: .init(repeating: 0.5), - color: color) - } else { nil } - } + var instances = world.instances instances.append( Instance( position: player.rayhitPos, diff --git a/Sources/Voxelotl/Player.swift b/Sources/Voxelotl/Player.swift index 514a7b1..bc27768 100644 --- a/Sources/Voxelotl/Player.swift +++ b/Sources/Voxelotl/Player.swift @@ -8,7 +8,7 @@ struct Player { to: .init(Self.radius, Self.height, Self.radius)) static let eyeLevel: Float = 1.4 - static let epsilon = Float.ulpOfOne * 10 + static let epsilon = Float.ulpOfOne * 20 static let accelerationCoeff: Float = 75 static let airAccelCoeff: Float = 3 @@ -40,7 +40,7 @@ struct Player { enum JumpInput { case off, press, held } - mutating func update(deltaTime: Float, chunk: inout Chunk) { + mutating func update(deltaTime: Float, world: World) { var turning: SIMD2 = .zero var movement: SIMD2 = .zero var flying: Int = .zero @@ -133,9 +133,9 @@ struct Player { self._velocity.y -= Self.gravityCoeff * deltaTime // Move & handle collision - let checkCorner = { (chunk: Chunk, bounds: AABB, corner: SIMD3) -> Optional in + let checkCorner = { (world: World, bounds: AABB, corner: SIMD3) -> Optional in let blockPos = SIMD3(floor(corner.x), floor(corner.y), floor(corner.z)) - if case BlockType.solid = chunk.getBlock(at: SIMD3(blockPos)).type { + if case BlockType.solid = world.getBlock(at: SIMD3(blockPos)).type { let blockGeometry = AABB(from: blockPos, to: blockPos + 1) if bounds.touching(blockGeometry) { return blockGeometry @@ -143,7 +143,7 @@ struct Player { } return nil } - let checkCollision = { (chunk: Chunk, position: SIMD3) -> Optional in + let checkCollision = { (world: World, position: SIMD3) -> Optional in let bounds = Self.bounds + position let corners: [SIMD3] = [ .init(bounds.left, bounds.bottom, bounds.far), @@ -160,14 +160,14 @@ struct Player { .init(bounds.right, bounds.top, bounds.near) ] for corner in corners { - if let geometry = checkCorner(chunk, bounds, corner) { + if let geometry = checkCorner(world, bounds, corner) { return geometry } } return nil } self._position.y += self._velocity.y * deltaTime - if let aabb = checkCollision(chunk, self._velocity.y > 0 ? self._position + .down * Self.epsilon : self.position) { + if let aabb = checkCollision(world, self._velocity.y > 0 ? self._position + .down * Self.epsilon : self.position) { if self._velocity.y < 0 { self._position.y = aabb.top + Self.epsilon self._onGround = true @@ -180,7 +180,7 @@ struct Player { self._onGround = false } self._position.x += self._velocity.x * deltaTime - if let aabb = checkCollision(chunk, self._position) { + if let aabb = checkCollision(world, self._position) { if self._velocity.x < 0 { self._position.x = aabb.right + Self.radius + Self.epsilon } else { @@ -189,7 +189,7 @@ struct Player { self._velocity.x = 0 } self._position.z += self._velocity.z * deltaTime - if let aabb = checkCollision(chunk, self._position) { + if let aabb = checkCollision(world, self._position) { if self._velocity.z < 0 { self._position.z = aabb.near + Self.radius + Self.epsilon } else { @@ -200,16 +200,16 @@ struct Player { // Block picking if let hit = raycast( - chunk: chunk, + world: world, origin: self.eyePosition, direction: .forward * simd_matrix3x3(self.eyeRotation), maxDistance: 3.8 ) { self.rayhitPos = hit.position if destroy { - chunk.setBlock(at: hit.map, type: .air) + world.setBlock(at: hit.map, type: .air) } else if place { - chunk.setBlock(at: hit.map.offset(by: hit.side), type: .solid(.white)) + world.setBlock(at: hit.map.offset(by: hit.side), type: .solid(.white)) } } diff --git a/Sources/Voxelotl/Raycast.swift b/Sources/Voxelotl/Raycast.swift index 26acdee..ab32966 100644 --- a/Sources/Voxelotl/Raycast.swift +++ b/Sources/Voxelotl/Raycast.swift @@ -1,7 +1,7 @@ import simd public func raycast( - chunk: Chunk, + world: World, origin rayPosition: SIMD3, direction: SIMD3, maxDistance: Float @@ -74,7 +74,7 @@ public func raycast( } // return a result if we hit something solid - if chunk.getBlock(at: mapPosition).type != .air { + if world.getBlock(at: mapPosition).type != .air { return .init( position: rayPosition + direction * distance, distance: distance, diff --git a/Sources/Voxelotl/World.swift b/Sources/Voxelotl/World.swift new file mode 100644 index 0000000..21a41ff --- /dev/null +++ b/Sources/Voxelotl/World.swift @@ -0,0 +1,61 @@ +import Foundation + +public class World { + private var _chunks: Dictionary, Chunk> + + public init() { + self._chunks = [:] + } + + func getBlock(at position: SIMD3) -> Block { + return if let chunk = self._chunks[position &>> Chunk.shift] { + chunk.getBlock(at: position) + } else { Block(.air) } + } + + func setBlock(at position: SIMD3, type: BlockType) { + self._chunks[position &>> Chunk.shift]?.setBlock(at: position, type: type) + } + + func generate(width: Int, height: Int, depth: Int, random: inout any RandomProvider) { + let noise = ImprovedPerlin(random: &random) + + for x in 0..(position) + return if fpos.y / Float(Chunk.size) + + noise.get(fpos * 0.05) * 1.1 + + noise.get(fpos * 0.1 + 500) * 0.5 + + noise.get(fpos * 0.3 + 100) * 0.23 < 0.6 { + .solid(.init( + r: Float16(noise.get(fpos * 0.1)), + g: Float16(noise.get(fpos * 0.1 + 10)), + b: Float16(noise.get(fpos * 0.1 + 100))).mix(.white, 0.6).linear) + } else { + .air + } + }) + self._chunks[chunkID] = chunk + } + } + } + } + + 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 } + } + } + } +}