forked from sagnik/Project_Velocity
119 lines
4.1 KiB
Swift
119 lines
4.1 KiB
Swift
import ARKit
|
|
import CoreLocation
|
|
import CoreMotion
|
|
import SceneKit
|
|
import SwiftUI
|
|
|
|
struct ARSunOverlayView: UIViewRepresentable {
|
|
@Binding var sunNodesReady: Bool
|
|
|
|
func makeCoordinator() -> Coordinator {
|
|
Coordinator(sunNodesReady: $sunNodesReady)
|
|
}
|
|
|
|
func makeUIView(context: Context) -> ARSCNView {
|
|
let view = ARSCNView(frame: .zero)
|
|
view.delegate = context.coordinator
|
|
view.scene = SCNScene()
|
|
view.automaticallyUpdatesLighting = true
|
|
|
|
let config = ARWorldTrackingConfiguration()
|
|
config.worldAlignment = .gravityAndHeading
|
|
view.session.run(config)
|
|
|
|
context.coordinator.attach(to: view)
|
|
return view
|
|
}
|
|
|
|
func updateUIView(_ uiView: ARSCNView, context: Context) {}
|
|
|
|
static func dismantleUIView(_ uiView: ARSCNView, coordinator: Coordinator) {
|
|
uiView.session.pause()
|
|
coordinator.stop()
|
|
}
|
|
|
|
final class Coordinator: NSObject, ARSCNViewDelegate, CLLocationManagerDelegate {
|
|
private let locationManager = CLLocationManager()
|
|
private let motionManager = CMMotionManager()
|
|
private weak var sceneView: ARSCNView?
|
|
private var heading: CLLocationDirection = 0
|
|
private var coordinate: CLLocationCoordinate2D?
|
|
@Binding private var sunNodesReady: Bool
|
|
|
|
init(sunNodesReady: Binding<Bool>) {
|
|
_sunNodesReady = sunNodesReady
|
|
super.init()
|
|
locationManager.delegate = self
|
|
locationManager.desiredAccuracy = kCLLocationAccuracyBest
|
|
locationManager.headingFilter = 1
|
|
locationManager.requestWhenInUseAuthorization()
|
|
locationManager.startUpdatingLocation()
|
|
locationManager.startUpdatingHeading()
|
|
startMotion()
|
|
}
|
|
|
|
func attach(to sceneView: ARSCNView) {
|
|
self.sceneView = sceneView
|
|
addSunPathNodesIfPossible()
|
|
}
|
|
|
|
func stop() {
|
|
motionManager.stopDeviceMotionUpdates()
|
|
locationManager.stopUpdatingHeading()
|
|
locationManager.stopUpdatingLocation()
|
|
}
|
|
|
|
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
|
guard coordinate == nil, let location = locations.last else { return }
|
|
coordinate = location.coordinate
|
|
addSunPathNodesIfPossible()
|
|
}
|
|
|
|
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
|
|
heading = newHeading.trueHeading > 0 ? newHeading.trueHeading : newHeading.magneticHeading
|
|
addSunPathNodesIfPossible()
|
|
}
|
|
|
|
private func startMotion() {
|
|
guard motionManager.isDeviceMotionAvailable else { return }
|
|
motionManager.deviceMotionUpdateInterval = 0.1
|
|
motionManager.startDeviceMotionUpdates()
|
|
}
|
|
|
|
private func addSunPathNodesIfPossible() {
|
|
guard
|
|
let sceneView,
|
|
let coordinate,
|
|
!sunNodesReady
|
|
else { return }
|
|
|
|
let samples = SunMath.sunPathSamples(for: Date(), coordinate: coordinate)
|
|
let sorted = samples.sorted { $0.key < $1.key }
|
|
let root = SCNNode()
|
|
let northOffset = (heading).radians
|
|
let radius: Float = 1.8
|
|
|
|
for (_, pos) in sorted {
|
|
let elevation = Float(pos.elevation.radians)
|
|
let azimuth = Float((pos.azimuth).radians) - Float(northOffset)
|
|
let x = radius * cos(elevation) * sin(azimuth)
|
|
let y = radius * sin(elevation)
|
|
let z = -radius * cos(elevation) * cos(azimuth)
|
|
|
|
let sphere = SCNSphere(radius: 0.03)
|
|
sphere.firstMaterial?.diffuse.contents = UIColor.systemYellow
|
|
let node = SCNNode(geometry: sphere)
|
|
node.position = SCNVector3(x, y, z)
|
|
root.addChildNode(node)
|
|
}
|
|
|
|
sceneView.scene.rootNode.addChildNode(root)
|
|
sunNodesReady = true
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension Double {
|
|
var radians: Double { self * .pi / 180.0 }
|
|
}
|