55 lines
1.6 KiB
Swift
55 lines
1.6 KiB
Swift
// OrbitalCamera.swift
|
|
// Blender-style orbit camera with pan, orbit, and dolly zoom.
|
|
|
|
import Observation
|
|
import simd
|
|
|
|
@Observable
|
|
final class OrbitalCamera {
|
|
|
|
var azimuth: Float = Float.pi * 0.25
|
|
var elevation: Float = Float.pi * 0.2
|
|
var target: SIMD3<Float> = .zero
|
|
var distance: Float = 7.0
|
|
|
|
private let minDistance: Float = 0.5
|
|
private let maxDistance: Float = 200.0
|
|
private let minElevation: Float = -Float.pi * 0.49
|
|
private let maxElevation: Float = Float.pi * 0.49
|
|
|
|
var eye: SIMD3<Float> {
|
|
let x = distance * cosf(elevation) * sinf(azimuth)
|
|
let y = distance * sinf(elevation)
|
|
let z = distance * cosf(elevation) * cosf(azimuth)
|
|
return target + SIMD3<Float>(x, y, z)
|
|
}
|
|
|
|
var viewMatrix: simd_float4x4 {
|
|
MathUtils.lookAt(eye: eye, center: target, up: SIMD3<Float>(0, 1, 0))
|
|
}
|
|
|
|
func orbit(deltaX: Float, deltaY: Float) {
|
|
azimuth -= deltaX * 0.008
|
|
elevation += deltaY * 0.008
|
|
elevation = min(max(elevation, minElevation), maxElevation)
|
|
}
|
|
|
|
func pan(deltaX: Float, deltaY: Float) {
|
|
let right = SIMD3<Float>(cosf(azimuth), 0, -sinf(azimuth))
|
|
let up = SIMD3<Float>(0, 1, 0)
|
|
let speed: Float = distance * 0.002
|
|
target += right * (-deltaX * speed) + up * (deltaY * speed)
|
|
}
|
|
|
|
func zoom(scale: Float) {
|
|
distance /= scale
|
|
distance = min(max(distance, minDistance), maxDistance)
|
|
}
|
|
|
|
func reset() {
|
|
azimuth = Float.pi * 0.25
|
|
elevation = Float.pi * 0.2
|
|
distance = 7.0
|
|
target = .zero
|
|
}
|
|
}
|