mirror of
				https://github.com/GayPizzaSpecifications/voxelotl-engine.git
				synced 2025-11-04 10:59:39 +00:00 
			
		
		
		
	block placing
This commit is contained in:
		@ -26,8 +26,6 @@ class Game: GameDelegate {
 | 
			
		||||
  var projection: matrix_float4x4 = .identity
 | 
			
		||||
  var chunk = Chunk(position: .zero)
 | 
			
		||||
 | 
			
		||||
  var rayhitPos = SIMD3<Float>.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)) *
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -25,6 +25,9 @@ struct Player {
 | 
			
		||||
  private var _onGround: Bool = false
 | 
			
		||||
  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 velocity: SIMD3<Float> { get { self._velocity } set { self._velocity = 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 }
 | 
			
		||||
 | 
			
		||||
  mutating func update(deltaTime: Float, chunk: Chunk) {
 | 
			
		||||
  mutating func update(deltaTime: Float, chunk: inout Chunk) {
 | 
			
		||||
    var turning: SIMD2<Float> = .zero
 | 
			
		||||
    var movement: SIMD2<Float> = .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<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))
 | 
			
		||||
      if case BlockType.solid = chunk.getBlock(at: SIMD3<Int>(blockPos)).type {
 | 
			
		||||
        let blockGeometry = AABB(from: blockPos, to: blockPos + 1)
 | 
			
		||||
@ -128,7 +140,7 @@ struct Player {
 | 
			
		||||
      }
 | 
			
		||||
      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 corners: [SIMD3<Float>] = [
 | 
			
		||||
        .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
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,6 @@
 | 
			
		||||
import simd
 | 
			
		||||
 | 
			
		||||
struct RaycastHit {
 | 
			
		||||
  let position: SIMD3<Float>
 | 
			
		||||
  let distance: Float
 | 
			
		||||
  let map: SIMD3<Int>
 | 
			
		||||
  let normal: SIMD3<Float>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func raycast(
 | 
			
		||||
public func raycast(
 | 
			
		||||
  chunk: Chunk,
 | 
			
		||||
  origin rayPosition: SIMD3<Float>,
 | 
			
		||||
  direction: SIMD3<Float>,
 | 
			
		||||
@ -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<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 }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user