// MathUtils.swift — right-handed camera matrices for Metal (NDC z in 0..1). import simd @inline(__always) func radians(_ deg: Float) -> Float { deg * .pi / 180 } // Right-handed perspective looking down -Z, mapping depth to [0, 1] (Metal). func perspectiveRH(fovyRadians: Float, aspect: Float, near: Float, far: Float) -> float4x4 { let ys = 1 / tan(fovyRadians * 0.5) let xs = ys / aspect let zs = far / (near - far) return float4x4(columns: ( SIMD4(xs, 0, 0, 0), SIMD4(0, ys, 0, 0), SIMD4(0, 0, zs, -1), SIMD4(0, 0, zs * near, 0) )) } // Right-handed look-at view matrix. func lookAtRH(eye: SIMD3, center: SIMD3, up: SIMD3) -> float4x4 { let z = simd_normalize(eye - center) // backward (toward viewer) let x = simd_normalize(simd_cross(up, z)) // right let y = simd_cross(z, x) // true up return float4x4(columns: ( SIMD4(x.x, y.x, z.x, 0), SIMD4(x.y, y.y, z.y, 0), SIMD4(x.z, y.z, z.z, 0), SIMD4(-simd_dot(x, eye), -simd_dot(y, eye), -simd_dot(z, eye), 1) )) } // Camera basis from yaw/pitch (degrees), matching pmove's convention: // forward = (sin(yaw)cos(pitch), sin(pitch), cos(yaw)cos(pitch)) func forwardVector(yawDeg: Float, pitchDeg: Float) -> SIMD3 { let y = radians(yawDeg), p = radians(pitchDeg) return SIMD3(sin(y) * cos(p), sin(p), cos(y) * cos(p)) }