From e087ed682f8e823abb5935e1ae10cd127aac848b Mon Sep 17 00:00:00 2001 From: a dinosaur Date: Sat, 24 Aug 2024 11:10:36 +1000 Subject: [PATCH] block placing --- Sources/Voxelotl/Game.swift | 22 +--------- Sources/Voxelotl/Keyboard.swift | 4 +- Sources/Voxelotl/Player.swift | 53 +++++++++++++++++------ Sources/Voxelotl/Raycast.swift | 74 ++++++++++++++++++++++++++------- 4 files changed, 103 insertions(+), 50 deletions(-) diff --git a/Sources/Voxelotl/Game.swift b/Sources/Voxelotl/Game.swift index 42f0a8f..6b383a3 100644 --- a/Sources/Voxelotl/Game.swift +++ b/Sources/Voxelotl/Game.swift @@ -26,8 +26,6 @@ class Game: GameDelegate { var projection: matrix_float4x4 = .identity var chunk = Chunk(position: .zero) - var rayhitPos = SIMD3.zero - init() { self.resetPlayer() self.generateWorld() @@ -79,10 +77,6 @@ class Game: GameDelegate { var destroy = false if let pad = GameController.current?.state { - if pad.pressed(.south) { - destroy = true - } - // Player reset if pad.pressed(.back) { self.resetPlayer() @@ -101,21 +95,9 @@ class Game: GameDelegate { self.generateWorld() } - self.player.update(deltaTime: deltaTime, chunk: chunk) + 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) { @@ -131,7 +113,7 @@ class Game: GameDelegate { } instances.append( Instance( - position: rayhitPos, + position: player.rayhitPos, scale: .init(repeating: 0.0725 * 0.5), rotation: .init(angle: totalTime * 3.0, axis: .init(0, 1, 0)) * diff --git a/Sources/Voxelotl/Keyboard.swift b/Sources/Voxelotl/Keyboard.swift index 0671dac..5c37ae6 100644 --- a/Sources/Voxelotl/Keyboard.swift +++ b/Sources/Voxelotl/Keyboard.swift @@ -4,7 +4,7 @@ public class Keyboard { public enum Keys { case a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z case right, left, up, down - case space + case space, tab } public static func down(_ key: Keys) -> Bool { @@ -90,6 +90,7 @@ internal extension Keyboard.Keys { case .up: SDLK_UP case .down: SDLK_DOWN case .space: SDLK_SPACE + case .tab: SDLK_TAB } } @@ -126,6 +127,7 @@ internal extension Keyboard.Keys { case .up: SDL_SCANCODE_UP case .down: SDL_SCANCODE_DOWN case .space: SDL_SCANCODE_SPACE + case .tab: SDL_SCANCODE_TAB } } } diff --git a/Sources/Voxelotl/Player.swift b/Sources/Voxelotl/Player.swift index 8f782d6..72a1cd6 100644 --- a/Sources/Voxelotl/Player.swift +++ b/Sources/Voxelotl/Player.swift @@ -25,6 +25,9 @@ struct Player { private var _onGround: Bool = false private var _shouldJump: Optional = .none + public var rayhitPos = SIMD3.zero + private var prevLeftTrigger: Float = 0, prevRightTrigger: Float = 0 + public var position: SIMD3 { get { self._position } set { self._position = newValue } } public var velocity: SIMD3 { get { self._velocity } set { self._velocity = newValue } } public var rotation: SIMD2 { get { self._rotation } set { self._rotation = newValue } } @@ -37,22 +40,31 @@ struct Player { enum JumpInput { case off, press, held } - mutating func update(deltaTime: Float, chunk: Chunk) { + mutating func update(deltaTime: Float, chunk: inout Chunk) { var turning: SIMD2 = .zero var movement: SIMD2 = .zero - var flying: Float = .zero + var flying: Int = .zero var jumpInput: JumpInput = .off + var destroy = false, place = false // Read controller input (if one is plugged in) if let pad = GameController.current?.state { turning += pad.rightStick.radialDeadzone(min: 0.1, max: 1) movement += pad.leftStick.cardinalDeadzone(min: 0.1, max: 1) - flying += pad.rightTrigger.axisDeadzone(0.01, 1) - pad.leftTrigger.axisDeadzone(0.01, 1) + flying += (pad.down(.rightBumper) ? 1 : 0) - (pad.down(.leftBumper) ? 1 : 0) if pad.pressed(.east) { jumpInput = .press } else if jumpInput != .press && pad.down(.east) { jumpInput = .held } + if pad.leftTrigger > 0.4 && prevLeftTrigger < 0.4 { + place = true + } + if pad.rightTrigger > 0.4 && prevRightTrigger < 0.4 { + destroy = true + } + prevLeftTrigger = pad.leftTrigger + prevRightTrigger = pad.rightTrigger } // Read keyboard input @@ -64,8 +76,9 @@ struct Player { if Keyboard.down(.down) { turning.y += 1 } if Keyboard.down(.left) { turning.x -= 1 } if Keyboard.down(.right) { turning.x += 1 } - if Keyboard.down(.q) { flying += 1 } - if Keyboard.down(.e) { flying -= 1 } + if Keyboard.down(.tab) { flying += 1 } + if Keyboard.pressed(.q) { place = true } + if Keyboard.pressed(.e) { destroy = true } if Keyboard.pressed(.space) { jumpInput = .press } else if jumpInput != .press && Keyboard.down(.space) { @@ -111,14 +124,13 @@ struct Player { self._velocity.z += (movement.y * rotc + movement.x * rots) * coeff * deltaTime // Flying and unflying - flying = flying.clamp(-1, 1) - self._velocity.y += flying * Self.flySpeedCoeff * deltaTime + self._velocity.y += Float(flying).clamp(-1, 1) * Self.flySpeedCoeff * deltaTime // Apply gravity self._velocity.y -= Self.gravityCoeff * deltaTime // Move & handle collision - let checkCorner = { (bounds: AABB, corner: SIMD3) -> Optional in + let checkCorner = { (chunk: Chunk, 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 { let blockGeometry = AABB(from: blockPos, to: blockPos + 1) @@ -128,7 +140,7 @@ struct Player { } return nil } - let checkCollision = { (position: SIMD3) -> Optional in + let checkCollision = { (chunk: Chunk, position: SIMD3) -> Optional in let bounds = Self.bounds + position let corners: [SIMD3] = [ .init(bounds.left, bounds.bottom, bounds.far), @@ -145,14 +157,14 @@ struct Player { .init(bounds.right, bounds.top, bounds.near) ] for corner in corners { - if let geometry = checkCorner(bounds, corner) { + if let geometry = checkCorner(chunk, bounds, corner) { return geometry } } return nil } self._position.y += self._velocity.y * deltaTime - if let aabb = checkCollision(self._velocity.y > 0 ? self._position + .down * Self.epsilon : self.position) { + if let aabb = checkCollision(chunk, 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 @@ -165,7 +177,7 @@ struct Player { self._onGround = false } self._position.x += self._velocity.x * deltaTime - if let aabb = checkCollision(self._position) { + if let aabb = checkCollision(chunk, self._position) { if self._velocity.x < 0 { self._position.x = aabb.right + Self.radius + Self.epsilon } else { @@ -174,7 +186,7 @@ struct Player { self._velocity.x = 0 } self._position.z += self._velocity.z * deltaTime - if let aabb = checkCollision(self._position) { + if let aabb = checkCollision(chunk, self._position) { if self._velocity.z < 0 { self._position.z = aabb.near + Self.radius + Self.epsilon } else { @@ -183,6 +195,21 @@ struct Player { self._velocity.z = 0 } + // Block picking + if let hit = raycast( + chunk: chunk, + origin: self.eyePosition, + direction: .forward * simd_matrix3x3(self.eyeRotation), + maxDistance: 3.666 + ) { + self.rayhitPos = hit.position + if destroy { + chunk.setBlock(at: hit.map, type: .air) + } else if place { + chunk.setBlock(at: hit.map.offset(by: hit.side), type: .solid(.white)) + } + } + // Ground friction if self._onGround { self._velocity.x /= 1.0 + Self.frictionCoeff * deltaTime diff --git a/Sources/Voxelotl/Raycast.swift b/Sources/Voxelotl/Raycast.swift index 334f93a..26acdee 100644 --- a/Sources/Voxelotl/Raycast.swift +++ b/Sources/Voxelotl/Raycast.swift @@ -1,13 +1,6 @@ import simd -struct RaycastHit { - let position: SIMD3 - let distance: Float - let map: SIMD3 - let normal: SIMD3 -} - -func raycast( +public func raycast( chunk: Chunk, origin rayPosition: SIMD3, direction: SIMD3, @@ -41,48 +34,97 @@ func raycast( } // Run digital differential analysis (3DDDA) - var side: Int + var side: RaycastSide while true { if sideDistance.x < sideDistance.y { if sideDistance.x < sideDistance.z { sideDistance.x += deltaDistance.x mapPosition.x += step.x - side = 0b100 + side = step.x > 0 ? .left : .right } else { sideDistance.z += deltaDistance.z mapPosition.z += step.z - side = 0b001 + side = step.z > 0 ? .front : .back } } else { if sideDistance.y < sideDistance.z { sideDistance.y += deltaDistance.y mapPosition.y += step.y - side = 0b010 + side = step.y > 0 ? .down : .up } else { sideDistance.z += deltaDistance.z mapPosition.z += step.z - side = 0b001 + side = step.z > 0 ? .front : .back } } - var distance: Float = if side == 0b100 { + // Compute distance + var distance: Float = if side.isX { abs(Float(mapPosition.x) - rayPosition.x + Float(1 - step.x) / 2) / direction.x - } else if side == 0b010 { + } else if side.isVertical { 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) + // Bail out if we've exeeded the max raycast distance if distance > maxDistance { return nil } + + // return a result if we hit something solid if chunk.getBlock(at: mapPosition).type != .air { return .init( position: rayPosition + direction * distance, distance: distance, map: mapPosition, - normal: .zero) + side: side) } } } + +public struct RaycastHit { + let position: SIMD3 + let distance: Float + let map: SIMD3 + let side: RaycastSide +} + +public enum RaycastSide { + case left, right + case down, up + case back, front +} + +public extension SIMD3 where Scalar == Int { + func offset(by side: RaycastSide) -> Self { + let ofs: Self = switch side { + case .right: .init( 1, 0, 0) + case .left: .init(-1, 0, 0) + case .up: .init( 0, 1, 0) + case .down: .init( 0, -1, 0) + case .back: .init( 0, 0, 1) + case .front: .init( 0, 0, -1) + } + return self &+ ofs + } +} + +public extension RaycastSide { + func normal() -> SIMD3 { + switch self { + case .left: .left + case .right: .right + case .down: .down + case .up: .up + case .back: .back + case .front: .forward + } + } + + @inline(__always) var isX: Bool { self == .left || self == .right } + @inline(__always) var isZ: Bool { self == .back || self == .front } + @inline(__always) var isHorizontal: Bool { self.isX || self.isZ } + @inline(__always) var isVertical: Bool { self == .up || self == .down } +}