// // ViewController.swift // TreeMeasure // // Created by Basti SK on 13.04.24. // import UIKit import AVFoundation import CoreMotion class ViewController: UIViewController { @IBOutlet weak var textView: UITextView! @IBOutlet weak var inputRefHeight: UITextField! @IBOutlet weak var statusText: UITextView! @IBOutlet weak var centerButton: UIButton! @IBOutlet weak var previewView: UIView! @IBOutlet var mainView: UIView! // Cam Variables var captureSession: AVCaptureSession! var videoPreviewLayer: AVCaptureVideoPreviewLayer! var globalQueue: OperationQueue! var backCamera: AVCaptureDevice! // VariablesForCalculations var globalDegrees: Double! var globalAttitude: Double! var globalGravityAngle: Double! var topPole: Double! var bottomPole: Double! var topTree: Double! // Motion Variables var motionManager: CMMotionManager! override func viewDidLoad() { super.viewDidLoad() // init motion manager motionManager = CMMotionManager(); // init vars for calculations topPole = 0; bottomPole = 0; topTree = 0; statusText.text = "To start, please snapshot the top of the reference height." // enable tap out of edit for ref. heigth let tap = UITapGestureRecognizer(target: self, action: #selector(UIInputViewController.dismissKeyboard)) tap.cancelsTouchesInView = false view.addGestureRecognizer(tap) } @objc func dismissKeyboard() { view.endEditing(true) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // start and prepare cam related things captureSession = AVCaptureSession() backCamera = AVCaptureDevice.default(for: AVMediaType.video) do { let input = try AVCaptureDeviceInput(device: backCamera) if(captureSession.canAddInput(input)) { captureSession.addInput(input) setupLivePreview() } } catch let error { print("Error Unable to initialize back camera: \(error.localizedDescription)") } // start monitoring the motion sensor startMotionMonitor(); } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) self.captureSession.stopRunning() motionManager.stopDeviceMotionUpdates() } @IBAction func addMarker() { let l = Double(inputRefHeight.text ?? "1.0") ?? 1.0 if(topPole == 0) { topPole = globalAttitude if globalGravityAngle < 0 { topPole = globalAttitude * -1 } statusText.text = "Snaphost Angle: " + String(format: "%.2f", globalDegrees) + "°.\nNext, snapshot the bottom of the reference height." return; } if(bottomPole == 0) { bottomPole = globalAttitude if globalGravityAngle < 0 { bottomPole = bottomPole * -1 } statusText.text = "Snapshot Angle: " + String(format: "%.2f", globalDegrees) + "°\nFinally, snapshot the top of the tree." return; } if(topTree == 0) { topTree = globalAttitude if globalGravityAngle < 0 { topTree = topTree * -1 } } if((topPole != 0) && (bottomPole != 0) && (topTree != 0)) { let alpha3 = topPole - Double.pi / 2 let alpha2 = bottomPole - Double.pi / 2 let alpha1 = topTree - Double.pi / 2 let e = abs(l / ( tan(alpha3) - tan(alpha2))) let h1 = e * tan(alpha1) let h2 = e * tan(alpha2) let h = abs(h1 - h2) statusText.text = "Height: " + String( format: "%.4f", h) + " m\nSnapshot top of the reference height to measure again." //reset topTree = 0 bottomPole = 0 topPole = 0 } } @IBAction func changeZoom() { if(backCamera.videoZoomFactor > 1) { setZoomFactor(factor: backCamera.videoZoomFactor + 10) } else { setZoomFactor(factor: backCamera.maxAvailableVideoZoomFactor) } } @IBAction func zoomOut() { if(backCamera.videoZoomFactor > 1) { setZoomFactor(factor: backCamera.videoZoomFactor - 1) } } @IBAction func zoomIn() { if(backCamera.videoZoomFactor < backCamera.maxAvailableVideoZoomFactor) { setZoomFactor(factor: backCamera.videoZoomFactor + 1) } } func setZoomFactor(factor: CGFloat) { do { try backCamera.lockForConfiguration() if(factor <= backCamera.maxAvailableVideoZoomFactor) { backCamera.videoZoomFactor = factor } backCamera.unlockForConfiguration() } catch let error { print("Error Unable to set zoom for back camera: \(error.localizedDescription)") } } func startMotionMonitor() { globalQueue = OperationQueue() if motionManager.isDeviceMotionAvailable == true { motionManager.deviceMotionUpdateInterval = 0.5 motionManager.startDeviceMotionUpdates(using: .xMagneticNorthZVertical, to: self.globalQueue, withHandler: { (data, error) in if let attitude = data?.attitude { // Get the pitch (in radians) and convert to degrees for display. self.globalDegrees = (attitude.pitch * 180.0/Double.pi) self.globalAttitude = attitude.pitch DispatchQueue.main.async { self.textView.text = "Angle: " + String(format: "%.2f", self.globalDegrees) + "°"; } } if let gravity = data?.gravity { self.globalGravityAngle = gravity.z; } // we are looking up! if self.globalGravityAngle > 0 { self.globalDegrees = 180 - self.globalDegrees; } }) } } func setupLivePreview() { videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) videoPreviewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill videoPreviewLayer.connection?.videoOrientation = .portrait previewView.layer.addSublayer(videoPreviewLayer) DispatchQueue.global(qos: .userInitiated).async { //[weak self] in self.captureSession.startRunning() DispatchQueue.main.async { self.videoPreviewLayer.frame = self.previewView.bounds } } } }