From 503c48404cca43e7d577a6d0cf5545e2da7cf284 Mon Sep 17 00:00:00 2001 From: a dinosaur Date: Wed, 28 Aug 2024 02:28:24 +1000 Subject: [PATCH] project mouse into view when clicking w/ mouse unlocked --- Sources/Voxelotl/Application.swift | 7 +- Sources/Voxelotl/Camera.swift | 81 +++++++++++++++++--- Sources/Voxelotl/Game.swift | 36 ++++----- Sources/Voxelotl/Input/Mouse.swift | 5 +- Sources/Voxelotl/Math/Matrix4x4.swift | 13 ++++ Sources/Voxelotl/Math/Rectangle.swift | 28 ++++++- Sources/Voxelotl/Math/VectorExtensions.swift | 59 ++++++++++++++ Sources/Voxelotl/Player.swift | 58 +++++++++----- Sources/Voxelotl/Raycast.swift | 46 +++++------ Sources/Voxelotl/Renderer/Renderer.swift | 48 ++++++------ Sources/Voxelotl/shader.metal | 4 +- Sources/Voxelotl/shadertypes.h | 4 +- 12 files changed, 278 insertions(+), 111 deletions(-) diff --git a/Sources/Voxelotl/Application.swift b/Sources/Voxelotl/Application.swift index d89bb2d..19a089f 100644 --- a/Sources/Voxelotl/Application.swift +++ b/Sources/Voxelotl/Application.swift @@ -38,11 +38,16 @@ public class Application { } // Get window metrics - var backBuffer = Size.zero + var backBuffer = Size.zero, windowSize = Size.zero guard SDL_GetWindowSizeInPixels(window, &backBuffer.w, &backBuffer.h) >= 0 else { printErr("SDL_GetWindowSizeInPixels() error: \(String(cString: SDL_GetError()))") return .exitFailure } + guard SDL_GetWindowSize(window, &windowSize.w, &windowSize.h) >= 0 else { + printErr("SDL_GetWindowSize() error: \(String(cString: SDL_GetError()))") + return .exitFailure + } + Mouse.instance.setDPI(scale: SIMD2(Size(backBuffer) / Size(windowSize))) // Create Metal renderer view = SDL_Metal_CreateView(window) diff --git a/Sources/Voxelotl/Camera.swift b/Sources/Voxelotl/Camera.swift index d3c3c86..25f4d1c 100644 --- a/Sources/Voxelotl/Camera.swift +++ b/Sources/Voxelotl/Camera.swift @@ -9,16 +9,18 @@ public final class Camera { static let viewProj = Self(rawValue: 1 << 2) } - private var _position: SIMD3 - private var _rotation: simd_quatf + private var _position = SIMD3.zero + private var _rotation = simd_quatf.identity private var _fieldOfView: Float private var _aspectRatio: Float private var _zNearFar: ClosedRange + private var _viewport: Rect private var _dirty: Dirty - private var _projection: matrix_float4x4 - private var _view: matrix_float4x4 - private var _viewProjection: matrix_float4x4 + private var _projection = matrix_identity_float4x4 + private var _view = matrix_identity_float4x4 + private var _viewProjection = matrix_identity_float4x4 + private var _invViewProjection = matrix_identity_float4x4 public var position: SIMD3 { get { self._position } @@ -59,8 +61,19 @@ public final class Camera { } } - public func setSize(_ size: Size) { - self.aspectRatio = Float(size.w) / Float(size.h) + public var viewport: Rect { + get { self._viewport } + set { + self._viewport = newValue + self.aspectRatio = Float(newValue.w) / Float(newValue.h) + } + } + public var size: Size { + get { Size(self._viewport.size) } + set { + self._viewport.size = Size(newValue) + self.aspectRatio = Float(newValue.w) / Float(newValue.h) + } } public var range: ClosedRange { @@ -94,20 +107,64 @@ public final class Camera { public var viewProjection: matrix_float4x4 { if !self._dirty.isEmpty { self._viewProjection = self.projection * self.view + self._invViewProjection = self._viewProjection.inverse self._dirty.remove(.viewProj) } return self._viewProjection } + public var invViewProjection: matrix_float4x4 { + if !self._dirty.isEmpty { + self._viewProjection = self.projection * self.view + self._invViewProjection = self._viewProjection.inverse + self._dirty.remove(.viewProj) + } + return self._invViewProjection + } init(fov: Float, size: Size, range: ClosedRange) { - self._position = .zero - self._rotation = .identity self._fieldOfView = fov.radians self._aspectRatio = Float(size.w) / Float(size.h) self._zNearFar = range + self._viewport = .init(origin: .zero, size: Size(size)) self._dirty = [ .projection, .view, .viewProj ] - self._projection = .init() - self._view = .init() - self._viewProjection = .init() + } + + public func screenRay(_ screen: SIMD2) -> SIMD3 { +#if true + simd_normalize(self.unproject(screen: SIMD3(screen, 1)) - self.unproject(screen: SIMD3(screen, 0))) +#else + let inverse = self.projection.inverse + var viewportCoord = screen - SIMD2(self._viewport.origin) + viewportCoord = (viewportCoord * 2) / SIMD2(self.viewport.size) - 1 + return simd_normalize(self._rotation * inverse.project(SIMD3(viewportCoord.x, -viewportCoord.y, 1))) +#endif + } + + public func unproject(screen2D: SIMD2) -> SIMD3 { + self.unproject(screen: SIMD3(screen2D, self._zNearFar.lowerBound)) + } + + public func unproject(screen: SIMD3) -> SIMD3 { + let inverse = self.invViewProjection + + var viewportCoord = screen.xy - SIMD2(self._viewport.origin) + viewportCoord = (viewportCoord * 2) / SIMD2(self._viewport.size) - 1 + +#if true + return inverse.project(SIMD3(viewportCoord.x, -viewportCoord.y, screen.z)) +#else + let projected = inverse * SIMD4(viewportCoord.x, -viewportCoord.y, screen.z, 1) + return projected.xyz * (1 / projected.w) +#endif + } + + public func project(world position: SIMD3) -> SIMD2 { + let viewport = self.viewProjection * SIMD4(position, 1) + if viewport.w == 0 { + // World point is exactly on focus point, screenpoint is undefined + return .zero + } + + return (viewport.xy + 1) * 0.5 * SIMD2(self._viewport.size) } } diff --git a/Sources/Voxelotl/Game.swift b/Sources/Voxelotl/Game.swift index 22f03ac..7bf0bbc 100644 --- a/Sources/Voxelotl/Game.swift +++ b/Sources/Voxelotl/Game.swift @@ -34,7 +34,7 @@ class Game: GameDelegate { } private func resetPlayer() { - self.player.position = .init(repeating: 0.5) + .init(0, Float(Chunk.size), 0) + self.player.position = .init(repeating: 0.5) + .up * Float(Chunk.size) self.player.velocity = .zero self.player.rotation = .init(.pi, 0) } @@ -47,13 +47,13 @@ class Game: GameDelegate { random = Xoroshiro128PlusPlus(seed: newSeed) #else random = PCG32Random(state: ( - UInt64(Arc4Random.instance.next()) | UInt64(Arc4Random.instance.next()) << 32, - UInt64(Arc4Random.instance.next()) | UInt64(Arc4Random.instance.next()) << 32)) + UInt64(Arc4Random.instance.next()) | UInt64(Arc4Random.instance.next()) << 32, + UInt64(Arc4Random.instance.next()) | UInt64(Arc4Random.instance.next()) << 32)) #endif #if DEBUG - self.world.generate(width: 2, height: 1, depth: 1, random: &random) + self.world.generate(width: 2, height: 1, depth: 1, random: &random) #else - self.world.generate(width: 5, height: 3, depth: 5, random: &random) + self.world.generate(width: 5, height: 3, depth: 5, random: &random) #endif } @@ -85,9 +85,7 @@ class Game: GameDelegate { self.generateWorld() } - self.player.update(deltaTime: deltaTime, world: world) - self.camera.position = player.eyePosition - self.camera.rotation = player.eyeRotation + self.player.update(deltaTime: deltaTime, world: world, camera: &camera) } func draw(_ renderer: Renderer, _ time: GameTime) { @@ -103,21 +101,23 @@ class Game: GameDelegate { gloss: 75) var instances = world.instances - instances.append( - Instance( - position: player.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 let position = player.rayhitPos { + instances.append( + Instance( + position: position, + 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, material: material, environment: env, camera: self.camera) } } func resize(_ size: Size) { - self.camera.setSize(size) + self.camera.size = size } } diff --git a/Sources/Voxelotl/Input/Mouse.swift b/Sources/Voxelotl/Input/Mouse.swift index b1fe84e..f44f16d 100644 --- a/Sources/Voxelotl/Input/Mouse.swift +++ b/Sources/Voxelotl/Input/Mouse.swift @@ -37,6 +37,7 @@ public class Mouse { private var _window: OpaquePointer! private var _captured: Bool = false + private var _dpiScale: SIMD2 = .one private var _abs: SIMD2 = .zero, _delta: SIMD2 = .zero private var _btns: Buttons = [], _btnImpulse: Buttons = [] @@ -48,7 +49,9 @@ public class Mouse { } } - private func getAbsolute() -> SIMD2 { self._abs } + internal func setDPI(scale: SIMD2) { self._dpiScale = scale } + + private func getAbsolute() -> SIMD2 { self._abs * self._dpiScale } private func getDelta() -> SIMD2 { self._delta } internal func buttonEvent(btn: UInt32, state: UInt8) { diff --git a/Sources/Voxelotl/Math/Matrix4x4.swift b/Sources/Voxelotl/Math/Matrix4x4.swift index d03612e..b9e4f57 100644 --- a/Sources/Voxelotl/Math/Matrix4x4.swift +++ b/Sources/Voxelotl/Math/Matrix4x4.swift @@ -88,6 +88,19 @@ public extension simd_float4x4 { .init(0, 0, z, -1), .init(0, 0, w, 0)) } + + func project(_ v: SIMD3) -> SIMD3 { + let t = self.transpose, v = SIMD4(v, 1) + return .init(simd_dot(v, t.columns.0), simd_dot(v, t.columns.1), simd_dot(v, t.columns.2)) + * (1 / simd_dot(v, t.columns.3)) + } + + static func * (lhs: Self, rhs: SIMD3) -> SIMD3 { + .init( + simd_dot(lhs.columns.0.xyz, rhs), + simd_dot(lhs.columns.1.xyz, rhs), + simd_dot(lhs.columns.2.xyz, rhs)) + } } extension simd_quatf { diff --git a/Sources/Voxelotl/Math/Rectangle.swift b/Sources/Voxelotl/Math/Rectangle.swift index cc5dabb..19b3acc 100644 --- a/Sources/Voxelotl/Math/Rectangle.swift +++ b/Sources/Voxelotl/Math/Rectangle.swift @@ -43,12 +43,32 @@ public struct Size: Equatable { extension Size where T: BinaryInteger { static var one: Self { .init(T(1), T(1)) } - init(_ other: Size) where O: BinaryInteger { + init(_ other: Size) { + self.init(T(other.w), T(other.h)) + } + init(_ other: Size) { self.init(T(other.w), T(other.h)) } } -struct Rect: Equatable { +extension Size where T: BinaryFloatingPoint { + init(_ other: Size) { + self.init(T(other.w), T(other.h)) + } + init(_ other: Size) { + self.init(T(other.w), T(other.h)) + } + + @inline(__always) public static func / (lhs: Self, rhs: Self) -> Self { .init(lhs.w / rhs.w, lhs.h / rhs.h) } +} + +extension SIMD2 where Scalar: AdditiveArithmetic { + init(_ size: Size) { + self.init(size.w, size.h) + } +} + +public struct Rect: Equatable { var x: T, y: T, w: T, h: T var origin: Point { @@ -76,12 +96,12 @@ struct Rect: Equatable { self.h = size.h } - @inline(__always) static func == (lhs: Self, rhs: Self) -> Bool { + @inline(__always) public static func == (lhs: Self, rhs: Self) -> Bool { lhs.x == rhs.x && lhs.y == rhs.y && lhs.w == rhs.w && lhs.h == rhs.h } } -extension Rect where T: AdditiveArithmetic { +public extension Rect where T: AdditiveArithmetic { var left: T { x } var right: T { x + w } var up: T { y } diff --git a/Sources/Voxelotl/Math/VectorExtensions.swift b/Sources/Voxelotl/Math/VectorExtensions.swift index e361ba6..6005368 100644 --- a/Sources/Voxelotl/Math/VectorExtensions.swift +++ b/Sources/Voxelotl/Math/VectorExtensions.swift @@ -1,3 +1,23 @@ +import simd + +extension SIMD3 { + var xy: SIMD2 { + get { .init(self.x, self.y) } + set { + self.x = newValue.x + self.y = newValue.y + } + } + + var xz: SIMD2 { + get { .init(self.x, self.z) } + set { + self.x = newValue.x + self.z = newValue.y + } + } +} + extension SIMD3 where Scalar: FloatingPoint { @inline(__always) static var X: Self { Self(1, 0, 0) } @inline(__always) static var Y: Self { Self(0, 1, 0) } @@ -10,3 +30,42 @@ extension SIMD3 where Scalar: FloatingPoint { @inline(__always) static var forward: Self { -Z } @inline(__always) static var back: Self { Z } } + +extension SIMD3 where Scalar == Float { + static func * (q: simd_quatf, v: Self) -> Self { +#if true + let q = simd_inverse(q) +#else + let q = simd_quatf(real: q.real, imag: -q.imag) +#endif + +#if true + var out = q.imag * 2 * simd_dot(q.imag, v) + out += v * (q.real * q.real - simd_dot(q.imag, q.imag)) + return out + simd_cross(q.imag, v) * 2 * q.real +#else + let uv = simd_cross(q.imag, v) + let uuv = simd_cross(q.imag, uv) + return v + ((uv * q.real) + uuv) * 2 +#endif + } +} + +extension SIMD4 { + var xy: SIMD2 { + get { .init(self.x, self.y) } + set { + self.x = newValue.x + self.y = newValue.y + } + } + + var xyz: SIMD3 { + get { .init(self.x, self.y, self.z) } + set { + self.x = newValue.x + self.y = newValue.y + self.z = newValue.z + } + } +} diff --git a/Sources/Voxelotl/Player.swift b/Sources/Voxelotl/Player.swift index bc27768..1e064e1 100644 --- a/Sources/Voxelotl/Player.swift +++ b/Sources/Voxelotl/Player.swift @@ -17,6 +17,7 @@ struct Player { static let flySpeedCoeff: Float = 36 static let jumpVelocity: Float = 7 static let maxVelocity: Float = 160 + static let blockReach: Float = 3.8 private var _position = SIMD3.zero private var _velocity = SIMD3.zero @@ -24,8 +25,9 @@ struct Player { private var _onGround: Bool = false private var _shouldJump: Optional = .none + private var _useMouseDir: Bool = false - public var rayhitPos = SIMD3.zero + public var rayhitPos: Optional> = nil private var prevLeftTrigger: Float = 0, prevRightTrigger: Float = 0 public var position: SIMD3 { get { self._position } set { self._position = newValue } } @@ -40,7 +42,7 @@ struct Player { enum JumpInput { case off, press, held } - mutating func update(deltaTime: Float, world: World) { + mutating func update(deltaTime: Float, world: World, camera: inout Camera) { var turning: SIMD2 = .zero var movement: SIMD2 = .zero var flying: Int = .zero @@ -49,7 +51,11 @@ struct Player { // Read controller input (if one is plugged in) if let pad = GameController.current?.state { - turning += pad.rightStick.radialDeadzone(min: 0.1, max: 1) + let turn = pad.rightStick.radialDeadzone(min: 0.1, max: 1) + if turn != .zero { + turning += turn + self._useMouseDir = false + } movement = pad.leftStick.cardinalDeadzone(min: 0.1, max: 1) flying += (pad.down(.rightBumper) ? 1 : 0) - (pad.down(.leftBumper) ? 1 : 0) if pad.pressed(.east) { @@ -86,6 +92,9 @@ struct Player { if Mouse.pressed(.right) { place = true } if Mouse.capture { self._rotation += Mouse.relative / 2048 * Float.pi + self._useMouseDir = false + } else if simd_length_squared(Mouse.relative) > Float.ulpOfOne { + self._useMouseDir = true } // Turning input @@ -121,10 +130,10 @@ struct Player { if movementMagnitude > 1.0 { movement /= movementMagnitude } - let rotc = cos(self._rotation.x), rots = sin(self._rotation.x) + let right = SIMD2(cos(self._rotation.x), sin(self._rotation.x)) + let forward = SIMD2(-right.y, right.x) let coeff = self._onGround ? Self.accelerationCoeff : Self.airAccelCoeff - self._velocity.x += (movement.x * rotc - movement.y * rots) * coeff * deltaTime - self._velocity.z += (movement.y * rotc + movement.x * rots) * coeff * deltaTime + self._velocity.xz += (right * movement.x + forward * movement.y) * coeff * deltaTime // Flying and unflying self._velocity.y += Float(flying).clamp(-1, 1) * Self.flySpeedCoeff * deltaTime @@ -198,25 +207,36 @@ struct Player { self._velocity.z = 0 } + // Update camera + camera.position = self.eyePosition + camera.rotation = self.eyeRotation + // Block picking - if let hit = raycast( - world: world, - origin: self.eyePosition, - direction: .forward * simd_matrix3x3(self.eyeRotation), - maxDistance: 3.8 - ) { - self.rayhitPos = hit.position - if destroy { - world.setBlock(at: hit.map, type: .air) - } else if place { - world.setBlock(at: hit.map.offset(by: hit.side), type: .solid(.white)) + let dir = !Mouse.capture && self._useMouseDir + ? camera.screenRay(Mouse.position) + : self.eyeRotation * .forward + if let hit = raycast(world: world, origin: self.eyePosition, direction: dir, maxDistance: Self.blockReach) { + if destroy || place { + if destroy { + world.setBlock(at: hit.map, type: .air) + } else { + world.setBlock(at: hit.map.offset(by: hit.side), type: .solid(.white)) + } + if let hit = raycast(world: world, origin: self.eyePosition, direction: dir, maxDistance: Self.blockReach) { + self.rayhitPos = hit.position + } else { + self.rayhitPos = nil + } + } else { + self.rayhitPos = hit.position } + } else { + self.rayhitPos = nil } // Ground friction if self._onGround { - self._velocity.x /= 1.0 + Self.frictionCoeff * deltaTime - self._velocity.z /= 1.0 + Self.frictionCoeff * deltaTime + self._velocity.xz /= 1.0 + Self.frictionCoeff * deltaTime } // Limit maximum velocity diff --git a/Sources/Voxelotl/Raycast.swift b/Sources/Voxelotl/Raycast.swift index ab32966..b73f8c9 100644 --- a/Sources/Voxelotl/Raycast.swift +++ b/Sources/Voxelotl/Raycast.swift @@ -6,31 +6,22 @@ public func raycast( direction: SIMD3, maxDistance: Float ) -> Optional { - let deltaDistance = abs(SIMD3(repeating: simd_length(direction)) / direction) + let directionLen = simd_length(direction) + let deltaDistance = SIMD3(direction.indices.map { + direction[$0] != 0.0 ? abs(directionLen / direction[$0]) : Float.greatestFiniteMagnitude + }) 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 + for i in 0..<3 { + if direction[i] < 0 { + step[i] = -1 + sideDistance[i] = (rayPosition[i] - Float(mapPosition[i])) * deltaDistance[i] + } else { + step[i] = 1 + sideDistance[i] = (Float(mapPosition[i]) + 1 - rayPosition[i]) * deltaDistance[i] + } } // Run digital differential analysis (3DDDA) @@ -59,24 +50,23 @@ public func raycast( } // Compute distance - var distance: Float = if side.isX { - abs(Float(mapPosition.x) - rayPosition.x + Float(1 - step.x) / 2) / direction.x + let distance: Float = if side.isX { + abs(Float(mapPosition.x) - rayPosition.x + Float(1 - step.x) * 0.5) * deltaDistance.x } 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) * 0.5) * deltaDistance.y } else { - abs(Float(mapPosition.z) - rayPosition.z + Float(1 - step.z) / 2) / direction.z + abs(Float(mapPosition.z) - rayPosition.z + Float(1 - step.z) * 0.5) * deltaDistance.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 + // Return a result if we hit something solid if world.getBlock(at: mapPosition).type != .air { return .init( - position: rayPosition + direction * distance, + position: rayPosition + direction / directionLen * distance, distance: distance, map: mapPosition, side: side) diff --git a/Sources/Voxelotl/Renderer/Renderer.swift b/Sources/Voxelotl/Renderer/Renderer.swift index 25c2a9d..ad90427 100644 --- a/Sources/Voxelotl/Renderer/Renderer.swift +++ b/Sources/Voxelotl/Renderer/Renderer.swift @@ -5,30 +5,30 @@ import simd import ShaderTypes fileprivate let cubeVertices: [ShaderVertex] = [ - .init(position: .init(-1, -1, 1, 1), normal: .init(.back, 0), texCoord: .init(0, 0)), - .init(position: .init( 1, -1, 1, 1), normal: .init(.back, 0), texCoord: .init(1, 0)), - .init(position: .init(-1, 1, 1, 1), normal: .init(.back, 0), texCoord: .init(0, 1)), - .init(position: .init( 1, 1, 1, 1), normal: .init(.back, 0), texCoord: .init(1, 1)), - .init(position: .init( 1, -1, 1, 1), normal: .init(.right, 0), texCoord: .init(0, 0)), - .init(position: .init( 1, -1, -1, 1), normal: .init(.right, 0), texCoord: .init(1, 0)), - .init(position: .init( 1, 1, 1, 1), normal: .init(.right, 0), texCoord: .init(0, 1)), - .init(position: .init( 1, 1, -1, 1), normal: .init(.right, 0), texCoord: .init(1, 1)), - .init(position: .init( 1, -1, -1, 1), normal: .init(.forward, 0), texCoord: .init(0, 0)), - .init(position: .init(-1, -1, -1, 1), normal: .init(.forward, 0), texCoord: .init(1, 0)), - .init(position: .init( 1, 1, -1, 1), normal: .init(.forward, 0), texCoord: .init(0, 1)), - .init(position: .init(-1, 1, -1, 1), normal: .init(.forward, 0), texCoord: .init(1, 1)), - .init(position: .init(-1, -1, -1, 1), normal: .init(.left, 0), texCoord: .init(0, 0)), - .init(position: .init(-1, -1, 1, 1), normal: .init(.left, 0), texCoord: .init(1, 0)), - .init(position: .init(-1, 1, -1, 1), normal: .init(.left, 0), texCoord: .init(0, 1)), - .init(position: .init(-1, 1, 1, 1), normal: .init(.left, 0), texCoord: .init(1, 1)), - .init(position: .init(-1, -1, -1, 1), normal: .init(.down, 0), texCoord: .init(0, 0)), - .init(position: .init( 1, -1, -1, 1), normal: .init(.down, 0), texCoord: .init(1, 0)), - .init(position: .init(-1, -1, 1, 1), normal: .init(.down, 0), texCoord: .init(0, 1)), - .init(position: .init( 1, -1, 1, 1), normal: .init(.down, 0), texCoord: .init(1, 1)), - .init(position: .init(-1, 1, 1, 1), normal: .init(.up, 0), texCoord: .init(0, 0)), - .init(position: .init( 1, 1, 1, 1), normal: .init(.up, 0), texCoord: .init(1, 0)), - .init(position: .init(-1, 1, -1, 1), normal: .init(.up, 0), texCoord: .init(0, 1)), - .init(position: .init( 1, 1, -1, 1), normal: .init(.up, 0), texCoord: .init(1, 1)), + .init(position: .init(-1, -1, 1), normal: .back, texCoord: .init(0, 0)), + .init(position: .init( 1, -1, 1), normal: .back, texCoord: .init(1, 0)), + .init(position: .init(-1, 1, 1), normal: .back, texCoord: .init(0, 1)), + .init(position: .init( 1, 1, 1), normal: .back, texCoord: .init(1, 1)), + .init(position: .init( 1, -1, 1), normal: .right, texCoord: .init(0, 0)), + .init(position: .init( 1, -1, -1), normal: .right, texCoord: .init(1, 0)), + .init(position: .init( 1, 1, 1), normal: .right, texCoord: .init(0, 1)), + .init(position: .init( 1, 1, -1), normal: .right, texCoord: .init(1, 1)), + .init(position: .init( 1, -1, -1), normal: .forward, texCoord: .init(0, 0)), + .init(position: .init(-1, -1, -1), normal: .forward, texCoord: .init(1, 0)), + .init(position: .init( 1, 1, -1), normal: .forward, texCoord: .init(0, 1)), + .init(position: .init(-1, 1, -1), normal: .forward, texCoord: .init(1, 1)), + .init(position: .init(-1, -1, -1), normal: .left, texCoord: .init(0, 0)), + .init(position: .init(-1, -1, 1), normal: .left, texCoord: .init(1, 0)), + .init(position: .init(-1, 1, -1), normal: .left, texCoord: .init(0, 1)), + .init(position: .init(-1, 1, 1), normal: .left, texCoord: .init(1, 1)), + .init(position: .init(-1, -1, -1), normal: .down, texCoord: .init(0, 0)), + .init(position: .init( 1, -1, -1), normal: .down, texCoord: .init(1, 0)), + .init(position: .init(-1, -1, 1), normal: .down, texCoord: .init(0, 1)), + .init(position: .init( 1, -1, 1), normal: .down, texCoord: .init(1, 1)), + .init(position: .init(-1, 1, 1), normal: .up, texCoord: .init(0, 0)), + .init(position: .init( 1, 1, 1), normal: .up, texCoord: .init(1, 0)), + .init(position: .init(-1, 1, -1), normal: .up, texCoord: .init(0, 1)), + .init(position: .init( 1, 1, -1), normal: .up, texCoord: .init(1, 1)), ] fileprivate let cubeIndices: [UInt16] = [ diff --git a/Sources/Voxelotl/shader.metal b/Sources/Voxelotl/shader.metal index db6f8be..02c99db 100644 --- a/Sources/Voxelotl/shader.metal +++ b/Sources/Voxelotl/shader.metal @@ -18,13 +18,13 @@ vertex FragmentInput vertexMain( constant VertexShaderUniforms& u [[buffer(VertexShaderInputIdxUniforms)]] ) { auto position = vtx[vertexID].position; - auto world = i[instanceID].model * position; + auto world = i[instanceID].model * float4(position, 1); FragmentInput out; out.position = u.projView * world; out.world = world.xyz; out.color = half4(i[instanceID].color); - out.normal = (i[instanceID].normalModel * vtx[vertexID].normal).xyz; + out.normal = (i[instanceID].normalModel * float4(vtx[vertexID].normal, 0)).xyz; out.texCoord = vtx[vertexID].texCoord; return out; } diff --git a/Sources/Voxelotl/shadertypes.h b/Sources/Voxelotl/shadertypes.h index fb8c84d..088ef00 100644 --- a/Sources/Voxelotl/shadertypes.h +++ b/Sources/Voxelotl/shadertypes.h @@ -24,8 +24,8 @@ typedef NS_ENUM(NSInteger, VertexShaderInputIdx) { }; typedef struct { - vector_float4 position; - vector_float4 normal; + vector_float3 position; + vector_float3 normal; vector_float2 texCoord; } ShaderVertex;