From 9dd56faa4e3146c0b5ed977ec09c09889dbcc67b Mon Sep 17 00:00:00 2001 From: a dinosaur Date: Tue, 20 Aug 2024 03:21:55 +1000 Subject: [PATCH] initial voxel system revamp --- Sources/Voxelotl/Chunk.swift | 93 ++++++++++++++++++++++++++++------- Sources/Voxelotl/Game.swift | 71 ++++++++------------------ Sources/Voxelotl/Player.swift | 12 ++--- 3 files changed, 102 insertions(+), 74 deletions(-) diff --git a/Sources/Voxelotl/Chunk.swift b/Sources/Voxelotl/Chunk.swift index ba9bbbd..0ca60d8 100644 --- a/Sources/Voxelotl/Chunk.swift +++ b/Sources/Voxelotl/Chunk.swift @@ -1,10 +1,15 @@ public struct Chunk { public static let chunkSize: Int = 16 + public static let blockCount = chunkSize * chunkSize * chunkSize + + private static let yStride = chunkSize + private static let zStride = chunkSize * chunkSize public let position: SIMD3 private var blocks: [Block] init(position: SIMD3, blocks: [Block]) { + assert(blocks.count == Self.blockCount) self.position = position self.blocks = blocks } @@ -13,24 +18,29 @@ public struct Chunk { self.position = position self.blocks = Array( repeating: BlockType.air, - count: Chunk.chunkSize * Chunk.chunkSize * Chunk.chunkSize + count: Self.blockCount ).map { type in Block(type) } } - func getBlockInternally(at position: SIMD3) -> Block { - blocks[position.x + position.y * Chunk.chunkSize + position.z * Chunk.chunkSize * Chunk.chunkSize] + func getBlock(at 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 { + Block(.air) + } else { + blocks[position.x + position.y * Self.yStride + position.z * Self.zStride] + } } - mutating func setBlockInternally(at position: SIMD3, type: BlockType) { - if position.x >= Chunk.chunkSize || position.y >= Chunk.chunkSize || position.z >= Chunk.chunkSize { - return - } - + mutating func setBlock(at 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 { + return + } - blocks[position.x + position.y * Chunk.chunkSize + position.z * Chunk.chunkSize * Chunk.chunkSize].type = type + blocks[position.x + position.y * Self.yStride + position.z * Self.zStride].type = type } mutating func fill(allBy calculation: () -> BlockType) { @@ -39,26 +49,75 @@ public struct Chunk { } } - func forEach(block perform: (SIMD3, Block) -> Void) { - for x in 0..) -> Void) { + for x in 0..(block transform: (Block, SIMD3) throws -> T) rethrows -> [T] { + assert(self.blocks.count == Self.blockCount) + + var out = [T]() + out.reserveCapacity(Self.blockCount) + + var position = SIMD3() + for i in self.blocks.indices { + out.append(try transform(blocks[i], position)) + position.x += 1 + if position.x == Self.chunkSize { + position.x = 0 + position.y += 1 + if position.y == Self.chunkSize { + position.y = 0 + position.z += 1 + } + } + } + + return out + } + + public func compactMap(block transform: (Block, SIMD3) throws -> T?) rethrows -> [T] { + assert(self.blocks.count == Self.blockCount) + + var out = [T]() + out.reserveCapacity(Self.blockCount >> 1) + + var position = SIMD3() + for i in self.blocks.indices { + if let element = try transform(blocks[i], position) { + out.append(element) + } + position.x += 1 + if position.x == Self.chunkSize { + position.x = 0 + position.y += 1 + if position.y == Self.chunkSize { + position.y = 0 + position.z += 1 + } + } + } + + return out + } } public enum BlockType: Equatable { case air - case solid(Color) + case solid(_ color: Color) } public struct Block { public var type: BlockType - - + public init(_ type: BlockType) { self.type = type } diff --git a/Sources/Voxelotl/Game.swift b/Sources/Voxelotl/Game.swift index 22c32e3..0614193 100644 --- a/Sources/Voxelotl/Game.swift +++ b/Sources/Voxelotl/Game.swift @@ -1,11 +1,6 @@ import simd import Foundation -struct Box { - var geometry: AABB - var color: Color = .white -} - struct Instance { let position: SIMD3 let scale: SIMD3 @@ -30,32 +25,24 @@ class Game: GameDelegate { var camera = Camera(fov: 60, size: .one, range: 0.06...900) var player = Player() var projection: matrix_float4x4 = .identity - - var boxes: [Box] = [] var chunk = Chunk(position: .zero) init() { player.position = SIMD3(0.5, Float(Chunk.chunkSize) + 0.5, 0.5) player.rotation = .init(.pi, 0) - let options: [BlockType] = [ - .air, - .solid(.blue), - .air, - .solid(.red), - .air, - .solid(.green), - .air, - .solid(.white), - .air, - .solid(.cyan), - .air, - .solid(.yellow), - .air, - .solid(.magenta), - .air, + let colors: [Color] = [ + .white, + .red, .blue, .green, + .magenta, .yellow, .cyan ] - chunk.fill(allBy: { options[Int(arc4random_uniform(UInt32(options.count)))] }) + chunk.fill(allBy: { + if (arc4random() & 0x1) == 0x1 { + .solid(colors[Int(arc4random_uniform(UInt32(colors.count)))]) + } else { + .air + } + }) } func fixedUpdate(_ time: GameTime) { @@ -72,29 +59,17 @@ class Game: GameDelegate { if let pad = GameController.current?.state { // Delete block underneath player if pad.pressed(.south) { - chunk.setBlockInternally(at: SIMD3(player.position + .down * 0.2), type: .air) + chunk.setBlock(at: SIMD3(player.position + .down * 0.2), type: .air) } // Player reset if pad.pressed(.back) { - player.position = SIMD3(0.5, Float(Chunk.chunkSize) + 0.5, 0.5) + player.position = .init(repeating: 0.5) + .init(0, Float(Chunk.chunkSize), 0) player.velocity = .zero player.rotation = .init(.pi, 0) } } - boxes = [] - chunk.forEach { position, block in - if block.type == .air { - return - } - if case let .solid(color) = block.type { - boxes.append(Box( - geometry: .fromUnitCube(position: SIMD3(position) + 0.5, scale: .init(repeating: 0.5)), - color: color)) - } - } - - player.update(deltaTime: deltaTime, boxes: boxes) + player.update(deltaTime: deltaTime, chunk: chunk) camera.position = player.eyePosition camera.rotation = player.eyeRotation } @@ -103,18 +78,14 @@ class Game: GameDelegate { let totalTime = Float(time.total.asFloat) let cubeSpeedMul: Float = 0.1 - var instances: [Instance] = boxes.map { - Instance( - position: $0.geometry.center, - scale: $0.geometry.size * 0.5, - color: $0.color) + let instances = 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(color).linear) + } else { nil } } - instances.append( - Instance( - position: .init(0, sin(totalTime * 1.5 * cubeSpeedMul) * 0.5, 0) * 2, - scale: .init(repeating: 0.5), - rotation: .init(angle: totalTime * 3.0 * cubeSpeedMul, axis: .init(0, 1, 0)), - color: .init(r: 0.5, g: 0.5, b: 1).linear)) renderer.batch(instances: instances, camera: self.camera) } diff --git a/Sources/Voxelotl/Player.swift b/Sources/Voxelotl/Player.swift index cc97058..f577a7f 100644 --- a/Sources/Voxelotl/Player.swift +++ b/Sources/Voxelotl/Player.swift @@ -33,7 +33,7 @@ struct Player { .init(angle: self._rotation.x, axis: .up) } - mutating func update(deltaTime: Float, boxes: [Box]) { + mutating func update(deltaTime: Float, chunk: Chunk) { if let pad = GameController.current?.state { // Turning input let turning = pad.rightStick.radialDeadzone(min: 0.1, max: 1) @@ -69,12 +69,10 @@ struct Player { // Move & handle collision let checkCollision = { (position: SIMD3) -> Optional in - for box in boxes { - let bounds = Self.bounds + position - if bounds.touching(box.geometry) { - return box.geometry - } - } + let bounds = Self.bounds + position + //if bounds.touching(blockGeometry) { + // return box.geometry + //} return nil } self._position.x += self._velocity.x * deltaTime