diff --git a/Sources/Voxelotl/CMakeLists.txt b/Sources/Voxelotl/CMakeLists.txt index 6e9f848..afffc60 100644 --- a/Sources/Voxelotl/CMakeLists.txt +++ b/Sources/Voxelotl/CMakeLists.txt @@ -38,6 +38,7 @@ add_executable(Voxelotl MACOSX_BUNDLE # Game logic classes Chunk.swift + Raycast.swift Player.swift Game.swift diff --git a/Sources/Voxelotl/Game.swift b/Sources/Voxelotl/Game.swift index ef53ded..42f0a8f 100644 --- a/Sources/Voxelotl/Game.swift +++ b/Sources/Voxelotl/Game.swift @@ -26,6 +26,8 @@ class Game: GameDelegate { var projection: matrix_float4x4 = .identity var chunk = Chunk(position: .zero) + var rayhitPos = SIMD3.zero + init() { self.resetPlayer() self.generateWorld() @@ -75,10 +77,10 @@ class Game: GameDelegate { let deltaTime = min(Float(time.delta.asFloat), 1.0 / 15) + var destroy = false if let pad = GameController.current?.state { - // Delete block underneath player if pad.pressed(.south) { - self.chunk.setBlock(at: SIMD3(player.position + .down * 0.2), type: .air) + destroy = true } // Player reset @@ -102,17 +104,40 @@ class Game: GameDelegate { self.player.update(deltaTime: deltaTime, chunk: chunk) self.camera.position = player.eyePosition self.camera.rotation = player.eyeRotation + + if let hit = raycast( + chunk: chunk, + origin: player.eyePosition, + direction: .forward * simd_matrix3x3(player.eyeRotation), + maxDistance: 3.333 + ) { + self.rayhitPos = hit.position + if destroy { + self.chunk.setBlock(at: hit.map, type: .air) + } + } } func draw(_ renderer: Renderer, _ time: GameTime) { - let instances = chunk.compactMap { block, position in + let totalTime = Float(time.total.asFloat) + + var instances = chunk.compactMap { block, position in if case let .solid(color) = block.type { Instance( - position: SIMD3(position) + 0.5, + position: SIMD3(chunk.position &+ position) + 0.5, scale: .init(repeating: 0.5), color: color) } else { nil } } + instances.append( + Instance( + position: rayhitPos, + scale: .init(repeating: 0.0725 * 0.5), + rotation: + .init(angle: totalTime * 3.0, axis: .init(0, 1, 0)) * + .init(angle: totalTime * 1.5, axis: .init(1, 0, 0)) * + .init(angle: totalTime * 0.7, axis: .init(0, 0, 1)), + color: .init(r: 0.5, g: 0.5, b: 1).linear)) if !instances.isEmpty { renderer.batch(instances: instances, camera: self.camera) } diff --git a/Sources/Voxelotl/Raycast.swift b/Sources/Voxelotl/Raycast.swift new file mode 100644 index 0000000..334f93a --- /dev/null +++ b/Sources/Voxelotl/Raycast.swift @@ -0,0 +1,88 @@ +import simd + +struct RaycastHit { + let position: SIMD3 + let distance: Float + let map: SIMD3 + let normal: SIMD3 +} + +func raycast( + chunk: Chunk, + origin rayPosition: SIMD3, + direction: SIMD3, + maxDistance: Float +) -> Optional { + let deltaDistance = abs(SIMD3(repeating: simd_length(direction)) / direction) + + var mapPosition = SIMD3(floor(rayPosition)) + var sideDistance: SIMD3 = .zero + var step: SIMD3 = .zero + if direction.x < 0 { + step.x = -1 + sideDistance.x = (rayPosition.x - Float(mapPosition.x)) * deltaDistance.x + } else { + step.x = 1 + sideDistance.x = (Float(mapPosition.x) + 1 - rayPosition.x) * deltaDistance.x + } + if direction.y < 0 { + step.y = -1 + sideDistance.y = (rayPosition.y - Float(mapPosition.y)) * deltaDistance.y + } else { + step.y = 1 + sideDistance.y = (Float(mapPosition.y) + 1 - rayPosition.y) * deltaDistance.y + } + if direction.z < 0 { + step.z = -1 + sideDistance.z = (rayPosition.z - Float(mapPosition.z)) * deltaDistance.z + } else { + step.z = 1 + sideDistance.z = (Float(mapPosition.z) + 1 - rayPosition.z) * deltaDistance.z + } + + // Run digital differential analysis (3DDDA) + var side: Int + while true { + if sideDistance.x < sideDistance.y { + if sideDistance.x < sideDistance.z { + sideDistance.x += deltaDistance.x + mapPosition.x += step.x + side = 0b100 + } else { + sideDistance.z += deltaDistance.z + mapPosition.z += step.z + side = 0b001 + } + } else { + if sideDistance.y < sideDistance.z { + sideDistance.y += deltaDistance.y + mapPosition.y += step.y + side = 0b010 + } else { + sideDistance.z += deltaDistance.z + mapPosition.z += step.z + side = 0b001 + } + } + + var distance: Float = if side == 0b100 { + abs(Float(mapPosition.x) - rayPosition.x + Float(1 - step.x) / 2) / direction.x + } else if side == 0b010 { + abs(Float(mapPosition.y) - rayPosition.y + Float(1 - step.y) / 2) / direction.y + } else { + abs(Float(mapPosition.z) - rayPosition.z + Float(1 - step.z) / 2) / direction.z + } + distance = abs(distance) + + if distance > maxDistance { + return nil + } + if chunk.getBlock(at: mapPosition).type != .air { + return .init( + position: rayPosition + direction * distance, + distance: distance, + map: mapPosition, + normal: .zero) + } + } +}