project mouse into view when clicking w/ mouse unlocked

This commit is contained in:
2024-08-28 02:28:24 +10:00
parent 160c9c8a68
commit 503c48404c
12 changed files with 278 additions and 111 deletions

View File

@ -38,11 +38,16 @@ public class Application {
}
// Get window metrics
var backBuffer = Size<Int32>.zero
var backBuffer = Size<Int32>.zero, windowSize = Size<Int32>.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<Float>(backBuffer) / Size<Float>(windowSize)))
// Create Metal renderer
view = SDL_Metal_CreateView(window)

View File

@ -9,16 +9,18 @@ public final class Camera {
static let viewProj = Self(rawValue: 1 << 2)
}
private var _position: SIMD3<Float>
private var _rotation: simd_quatf
private var _position = SIMD3<Float>.zero
private var _rotation = simd_quatf.identity
private var _fieldOfView: Float
private var _aspectRatio: Float
private var _zNearFar: ClosedRange<Float>
private var _viewport: Rect<Float>
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<Float> {
get { self._position }
@ -59,8 +61,19 @@ public final class Camera {
}
}
public func setSize(_ size: Size<Int>) {
self.aspectRatio = Float(size.w) / Float(size.h)
public var viewport: Rect<Float> {
get { self._viewport }
set {
self._viewport = newValue
self.aspectRatio = Float(newValue.w) / Float(newValue.h)
}
}
public var size: Size<Int> {
get { Size<Int>(self._viewport.size) }
set {
self._viewport.size = Size<Float>(newValue)
self.aspectRatio = Float(newValue.w) / Float(newValue.h)
}
}
public var range: ClosedRange<Float> {
@ -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<Int>, range: ClosedRange<Float>) {
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<Float>(size))
self._dirty = [ .projection, .view, .viewProj ]
self._projection = .init()
self._view = .init()
self._viewProjection = .init()
}
public func screenRay(_ screen: SIMD2<Float>) -> SIMD3<Float> {
#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<Float>) -> SIMD3<Float> {
self.unproject(screen: SIMD3(screen2D, self._zNearFar.lowerBound))
}
public func unproject(screen: SIMD3<Float>) -> SIMD3<Float> {
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<Float>) -> SIMD2<Float> {
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)
}
}

View File

@ -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<Int>) {
self.camera.setSize(size)
self.camera.size = size
}
}

View File

@ -37,6 +37,7 @@ public class Mouse {
private var _window: OpaquePointer!
private var _captured: Bool = false
private var _dpiScale: SIMD2<Float> = .one
private var _abs: SIMD2<Float> = .zero, _delta: SIMD2<Float> = .zero
private var _btns: Buttons = [], _btnImpulse: Buttons = []
@ -48,7 +49,9 @@ public class Mouse {
}
}
private func getAbsolute() -> SIMD2<Float> { self._abs }
internal func setDPI(scale: SIMD2<Float>) { self._dpiScale = scale }
private func getAbsolute() -> SIMD2<Float> { self._abs * self._dpiScale }
private func getDelta() -> SIMD2<Float> { self._delta }
internal func buttonEvent(btn: UInt32, state: UInt8) {

View File

@ -88,6 +88,19 @@ public extension simd_float4x4 {
.init(0, 0, z, -1),
.init(0, 0, w, 0))
}
func project(_ v: SIMD3<Float>) -> SIMD3<Float> {
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<Float>) -> SIMD3<Float> {
.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 {

View File

@ -43,12 +43,32 @@ public struct Size<T: AdditiveArithmetic>: Equatable {
extension Size where T: BinaryInteger {
static var one: Self { .init(T(1), T(1)) }
init<O>(_ other: Size<O>) where O: BinaryInteger {
init<O: BinaryInteger>(_ other: Size<O>) {
self.init(T(other.w), T(other.h))
}
init<O: BinaryFloatingPoint>(_ other: Size<O>) {
self.init(T(other.w), T(other.h))
}
}
struct Rect<T: AdditiveArithmetic>: Equatable {
extension Size where T: BinaryFloatingPoint {
init<O: BinaryInteger>(_ other: Size<O>) {
self.init(T(other.w), T(other.h))
}
init<O: BinaryFloatingPoint>(_ other: Size<O>) {
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<Scalar>) {
self.init(size.w, size.h)
}
}
public struct Rect<T: AdditiveArithmetic>: Equatable {
var x: T, y: T, w: T, h: T
var origin: Point<T> {
@ -76,12 +96,12 @@ struct Rect<T: AdditiveArithmetic>: 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 }

View File

@ -1,3 +1,23 @@
import simd
extension SIMD3 {
var xy: SIMD2<Scalar> {
get { .init(self.x, self.y) }
set {
self.x = newValue.x
self.y = newValue.y
}
}
var xz: SIMD2<Scalar> {
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<Scalar> {
get { .init(self.x, self.y) }
set {
self.x = newValue.x
self.y = newValue.y
}
}
var xyz: SIMD3<Scalar> {
get { .init(self.x, self.y, self.z) }
set {
self.x = newValue.x
self.y = newValue.y
self.z = newValue.z
}
}
}

View File

@ -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<Float>.zero
private var _velocity = SIMD3<Float>.zero
@ -24,8 +25,9 @@ struct Player {
private var _onGround: Bool = false
private var _shouldJump: Optional<Float> = .none
private var _useMouseDir: Bool = false
public var rayhitPos = SIMD3<Float>.zero
public var rayhitPos: Optional<SIMD3<Float>> = nil
private var prevLeftTrigger: Float = 0, prevRightTrigger: Float = 0
public var position: SIMD3<Float> { 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<Float> = .zero
var movement: SIMD2<Float> = .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

View File

@ -6,31 +6,22 @@ public func raycast(
direction: SIMD3<Float>,
maxDistance: Float
) -> Optional<RaycastHit> {
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<Int>(floor(rayPosition))
var sideDistance: SIMD3<Float> = .zero
var step: SIMD3<Int> = .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)

View File

@ -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] = [

View File

@ -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;
}

View File

@ -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;