block placing

This commit is contained in:
2024-08-24 11:10:36 +10:00
parent 7d6d361fde
commit e087ed682f
4 changed files with 103 additions and 50 deletions

View File

@ -26,8 +26,6 @@ class Game: GameDelegate {
var projection: matrix_float4x4 = .identity var projection: matrix_float4x4 = .identity
var chunk = Chunk(position: .zero) var chunk = Chunk(position: .zero)
var rayhitPos = SIMD3<Float>.zero
init() { init() {
self.resetPlayer() self.resetPlayer()
self.generateWorld() self.generateWorld()
@ -79,10 +77,6 @@ class Game: GameDelegate {
var destroy = false var destroy = false
if let pad = GameController.current?.state { if let pad = GameController.current?.state {
if pad.pressed(.south) {
destroy = true
}
// Player reset // Player reset
if pad.pressed(.back) { if pad.pressed(.back) {
self.resetPlayer() self.resetPlayer()
@ -101,21 +95,9 @@ class Game: GameDelegate {
self.generateWorld() self.generateWorld()
} }
self.player.update(deltaTime: deltaTime, chunk: chunk) self.player.update(deltaTime: deltaTime, chunk: &chunk)
self.camera.position = player.eyePosition self.camera.position = player.eyePosition
self.camera.rotation = player.eyeRotation 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) { func draw(_ renderer: Renderer, _ time: GameTime) {
@ -131,7 +113,7 @@ class Game: GameDelegate {
} }
instances.append( instances.append(
Instance( Instance(
position: rayhitPos, position: player.rayhitPos,
scale: .init(repeating: 0.0725 * 0.5), scale: .init(repeating: 0.0725 * 0.5),
rotation: rotation:
.init(angle: totalTime * 3.0, axis: .init(0, 1, 0)) * .init(angle: totalTime * 3.0, axis: .init(0, 1, 0)) *

View File

@ -4,7 +4,7 @@ public class Keyboard {
public enum Keys { 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 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 right, left, up, down
case space case space, tab
} }
public static func down(_ key: Keys) -> Bool { public static func down(_ key: Keys) -> Bool {
@ -90,6 +90,7 @@ internal extension Keyboard.Keys {
case .up: SDLK_UP case .up: SDLK_UP
case .down: SDLK_DOWN case .down: SDLK_DOWN
case .space: SDLK_SPACE case .space: SDLK_SPACE
case .tab: SDLK_TAB
} }
} }
@ -126,6 +127,7 @@ internal extension Keyboard.Keys {
case .up: SDL_SCANCODE_UP case .up: SDL_SCANCODE_UP
case .down: SDL_SCANCODE_DOWN case .down: SDL_SCANCODE_DOWN
case .space: SDL_SCANCODE_SPACE case .space: SDL_SCANCODE_SPACE
case .tab: SDL_SCANCODE_TAB
} }
} }
} }

View File

@ -25,6 +25,9 @@ struct Player {
private var _onGround: Bool = false private var _onGround: Bool = false
private var _shouldJump: Optional<Float> = .none private var _shouldJump: Optional<Float> = .none
public var rayhitPos = SIMD3<Float>.zero
private var prevLeftTrigger: Float = 0, prevRightTrigger: Float = 0
public var position: SIMD3<Float> { get { self._position } set { self._position = newValue } } public var position: SIMD3<Float> { get { self._position } set { self._position = newValue } }
public var velocity: SIMD3<Float> { get { self._velocity } set { self._velocity = newValue } } public var velocity: SIMD3<Float> { get { self._velocity } set { self._velocity = newValue } }
public var rotation: SIMD2<Float> { get { self._rotation } set { self._rotation = newValue } } public var rotation: SIMD2<Float> { get { self._rotation } set { self._rotation = newValue } }
@ -37,22 +40,31 @@ struct Player {
enum JumpInput { case off, press, held } enum JumpInput { case off, press, held }
mutating func update(deltaTime: Float, chunk: Chunk) { mutating func update(deltaTime: Float, chunk: inout Chunk) {
var turning: SIMD2<Float> = .zero var turning: SIMD2<Float> = .zero
var movement: SIMD2<Float> = .zero var movement: SIMD2<Float> = .zero
var flying: Float = .zero var flying: Int = .zero
var jumpInput: JumpInput = .off var jumpInput: JumpInput = .off
var destroy = false, place = false
// Read controller input (if one is plugged in) // Read controller input (if one is plugged in)
if let pad = GameController.current?.state { if let pad = GameController.current?.state {
turning += pad.rightStick.radialDeadzone(min: 0.1, max: 1) turning += pad.rightStick.radialDeadzone(min: 0.1, max: 1)
movement += pad.leftStick.cardinalDeadzone(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) { if pad.pressed(.east) {
jumpInput = .press jumpInput = .press
} else if jumpInput != .press && pad.down(.east) { } else if jumpInput != .press && pad.down(.east) {
jumpInput = .held 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 // Read keyboard input
@ -64,8 +76,9 @@ struct Player {
if Keyboard.down(.down) { turning.y += 1 } if Keyboard.down(.down) { turning.y += 1 }
if Keyboard.down(.left) { turning.x -= 1 } if Keyboard.down(.left) { turning.x -= 1 }
if Keyboard.down(.right) { turning.x += 1 } if Keyboard.down(.right) { turning.x += 1 }
if Keyboard.down(.q) { flying += 1 } if Keyboard.down(.tab) { flying += 1 }
if Keyboard.down(.e) { flying -= 1 } if Keyboard.pressed(.q) { place = true }
if Keyboard.pressed(.e) { destroy = true }
if Keyboard.pressed(.space) { if Keyboard.pressed(.space) {
jumpInput = .press jumpInput = .press
} else if jumpInput != .press && Keyboard.down(.space) { } else if jumpInput != .press && Keyboard.down(.space) {
@ -111,14 +124,13 @@ struct Player {
self._velocity.z += (movement.y * rotc + movement.x * rots) * coeff * deltaTime self._velocity.z += (movement.y * rotc + movement.x * rots) * coeff * deltaTime
// Flying and unflying // Flying and unflying
flying = flying.clamp(-1, 1) self._velocity.y += Float(flying).clamp(-1, 1) * Self.flySpeedCoeff * deltaTime
self._velocity.y += flying * Self.flySpeedCoeff * deltaTime
// Apply gravity // Apply gravity
self._velocity.y -= Self.gravityCoeff * deltaTime self._velocity.y -= Self.gravityCoeff * deltaTime
// Move & handle collision // Move & handle collision
let checkCorner = { (bounds: AABB, corner: SIMD3<Float>) -> Optional<AABB> in let checkCorner = { (chunk: Chunk, bounds: AABB, corner: SIMD3<Float>) -> Optional<AABB> in
let blockPos = SIMD3(floor(corner.x), floor(corner.y), floor(corner.z)) let blockPos = SIMD3(floor(corner.x), floor(corner.y), floor(corner.z))
if case BlockType.solid = chunk.getBlock(at: SIMD3<Int>(blockPos)).type { if case BlockType.solid = chunk.getBlock(at: SIMD3<Int>(blockPos)).type {
let blockGeometry = AABB(from: blockPos, to: blockPos + 1) let blockGeometry = AABB(from: blockPos, to: blockPos + 1)
@ -128,7 +140,7 @@ struct Player {
} }
return nil return nil
} }
let checkCollision = { (position: SIMD3<Float>) -> Optional<AABB> in let checkCollision = { (chunk: Chunk, position: SIMD3<Float>) -> Optional<AABB> in
let bounds = Self.bounds + position let bounds = Self.bounds + position
let corners: [SIMD3<Float>] = [ let corners: [SIMD3<Float>] = [
.init(bounds.left, bounds.bottom, bounds.far), .init(bounds.left, bounds.bottom, bounds.far),
@ -145,14 +157,14 @@ struct Player {
.init(bounds.right, bounds.top, bounds.near) .init(bounds.right, bounds.top, bounds.near)
] ]
for corner in corners { for corner in corners {
if let geometry = checkCorner(bounds, corner) { if let geometry = checkCorner(chunk, bounds, corner) {
return geometry return geometry
} }
} }
return nil return nil
} }
self._position.y += self._velocity.y * deltaTime 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 { if self._velocity.y < 0 {
self._position.y = aabb.top + Self.epsilon self._position.y = aabb.top + Self.epsilon
self._onGround = true self._onGround = true
@ -165,7 +177,7 @@ struct Player {
self._onGround = false self._onGround = false
} }
self._position.x += self._velocity.x * deltaTime 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 { if self._velocity.x < 0 {
self._position.x = aabb.right + Self.radius + Self.epsilon self._position.x = aabb.right + Self.radius + Self.epsilon
} else { } else {
@ -174,7 +186,7 @@ struct Player {
self._velocity.x = 0 self._velocity.x = 0
} }
self._position.z += self._velocity.z * deltaTime 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 { if self._velocity.z < 0 {
self._position.z = aabb.near + Self.radius + Self.epsilon self._position.z = aabb.near + Self.radius + Self.epsilon
} else { } else {
@ -183,6 +195,21 @@ struct Player {
self._velocity.z = 0 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 // Ground friction
if self._onGround { if self._onGround {
self._velocity.x /= 1.0 + Self.frictionCoeff * deltaTime self._velocity.x /= 1.0 + Self.frictionCoeff * deltaTime

View File

@ -1,13 +1,6 @@
import simd import simd
struct RaycastHit { public func raycast(
let position: SIMD3<Float>
let distance: Float
let map: SIMD3<Int>
let normal: SIMD3<Float>
}
func raycast(
chunk: Chunk, chunk: Chunk,
origin rayPosition: SIMD3<Float>, origin rayPosition: SIMD3<Float>,
direction: SIMD3<Float>, direction: SIMD3<Float>,
@ -41,48 +34,97 @@ func raycast(
} }
// Run digital differential analysis (3DDDA) // Run digital differential analysis (3DDDA)
var side: Int var side: RaycastSide
while true { while true {
if sideDistance.x < sideDistance.y { if sideDistance.x < sideDistance.y {
if sideDistance.x < sideDistance.z { if sideDistance.x < sideDistance.z {
sideDistance.x += deltaDistance.x sideDistance.x += deltaDistance.x
mapPosition.x += step.x mapPosition.x += step.x
side = 0b100 side = step.x > 0 ? .left : .right
} else { } else {
sideDistance.z += deltaDistance.z sideDistance.z += deltaDistance.z
mapPosition.z += step.z mapPosition.z += step.z
side = 0b001 side = step.z > 0 ? .front : .back
} }
} else { } else {
if sideDistance.y < sideDistance.z { if sideDistance.y < sideDistance.z {
sideDistance.y += deltaDistance.y sideDistance.y += deltaDistance.y
mapPosition.y += step.y mapPosition.y += step.y
side = 0b010 side = step.y > 0 ? .down : .up
} else { } else {
sideDistance.z += deltaDistance.z sideDistance.z += deltaDistance.z
mapPosition.z += step.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 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 abs(Float(mapPosition.y) - rayPosition.y + Float(1 - step.y) / 2) / direction.y
} else { } else {
abs(Float(mapPosition.z) - rayPosition.z + Float(1 - step.z) / 2) / direction.z abs(Float(mapPosition.z) - rayPosition.z + Float(1 - step.z) / 2) / direction.z
} }
distance = abs(distance) distance = abs(distance)
// Bail out if we've exeeded the max raycast distance
if distance > maxDistance { if distance > maxDistance {
return nil return nil
} }
// return a result if we hit something solid
if chunk.getBlock(at: mapPosition).type != .air { if chunk.getBlock(at: mapPosition).type != .air {
return .init( return .init(
position: rayPosition + direction * distance, position: rayPosition + direction * distance,
distance: distance, distance: distance,
map: mapPosition, map: mapPosition,
normal: .zero) side: side)
} }
} }
} }
public struct RaycastHit {
let position: SIMD3<Float>
let distance: Float
let map: SIMD3<Int>
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<T: FloatingPoint>() -> SIMD3<T> {
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 }
}