AR項目實踐二:ar直尺

1. 搭載初始代碼

這一部分比較簡單就不再秒速了

xib文件


import UIKit
import ARKit
import SceneKit


class ViewController: UIViewController {

    @IBOutlet weak var scenview: ARSCNView!

    @IBOutlet weak var targetImg: UIImageView!
    @IBOutlet weak var infoLalbel: UILabel!
    @IBOutlet weak var stackView: UIStackView!

    var session = ARSession()//session
    var configuration = ARWorldTrackingConfiguration()//監聽



    override func viewDidLoad() {
        super.viewDidLoad()

        self.setup()
        // Do any additional setup after loading the view, typically from a nib.


    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        //全局追蹤的方法
        session.run(configuration, options: [.resetTracking,.removeExistingAnchors])

    }



    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        session.pause()
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }



    //mark:setup
    func setup()  {
        scenview.delegate = self

        scenview.session = session

        infoLalbel.text = "初始化中..."
    }


}



extension ViewController: ARSCNViewDelegate {
     //改變攝像頭的焦點就能調用這個方法
    func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {

//        DispatchQueue.main.async {
//            self.scanWorld()
//        }

    }

    func session(_ session: ARSession, didFailWithError error: Error) {

        infoLalbel.text = "錯誤"
    }

    func sessionWasInterrupted(_ session: ARSession) {

        infoLalbel.text = "中斷~"
    }

    func sessionInterruptionEnded(_ session: ARSession) {

        infoLalbel.text = "結束"
    }

}

2. 獲取三維座標,獲取相機的實時位置

編寫兩個擴展ARSCNView+Extension 和 SCNVector3 + Extension

ARSCNView+Extension

extension ARSCNView{
    //    拿到三維座標
    func worldVector(for position: CGPoint) -> SCNVector3?{

        let results = self.hitTest(position, types: [.featurePoint])


        guard let result = results.first else {
            return nil
        }


        //將拿到的點轉化爲三維座標
        return SCNVector3.positionTrasform(result.worldTransform)
    }

SCNVector3 + Extension

 //  拿到鏡頭的座標
    static func positionTrasform(_ tranform: matrix_float4x4) -> SCNVector3 {

        return SCNVector3Make(tranform.columns.3.x, tranform.columns.3.y, tranform.columns.3.z)
}

知識補充

let results = self.hitTest(position, types: [.featurePoint])
  1. 這個方法,是用來來搜索 ARSession 檢測到的maodian還有真是世界的兌現, 不是view 裏 的 SceneKit. 裏 的內容
  2. types類型分析
    • featurePoint:返回結果的3D特徵點
    • estimatedHorizontalPlane:表示此次 Hit-testing 過程希望返回當前圖像中 Hit-testing 射線經過的預估平面。預估平面表示 ARKit 當前檢測到一個可能是平面的信息,但當前尚未確定是平面
    • existingPlane:結果類型與現有的平面相交的錨
    • intersecting:與現有平面錨相交的結果類型,同時考慮到飛機的範圍

可以看看這時候返回的結果
這裏寫圖片描述

3. 計算距離

在三維座標下滿足:
記A(x1,y1,z1),B(x2,y2,z2),則A,B之間的距離爲
d=√[(x1-x2)^2+(y1-y2)^2+(z1-z2)^2]

代碼爲

    //    求距離
    func distance(form vector: SCNVector3) -> Float{

        let distanceX = self.x - vector.x
        let distanceY = self.y - vector.y
        let distanceZ = self.z - vector.z

        return sqrt((distanceX * distanceX) + (distanceY * distanceY) + (distanceZ * distanceZ))

    }

創建line節點

//劃線,在ar的世界裏 萬物都是節點 所以返回scnnode

    func line(vector:SCNVector3 , color:UIColor) -> SCNNode {

        let indices : [UInt32] = [0,1]//指數
        // 0是yiwei
        //1是二維
        //1.創建集合容器
        let source = SCNGeometrySource(vertices: [self,vector]) // 創建一個幾何容器

        //2.創建集合元素
        let element = SCNGeometryElement(indices: indices, primitiveType: .line)//用線的方式來創造一個幾何元素(線)

      //3.創建集合
        let geomtry = SCNGeometry(sources: [source], elements: [element])//幾何

        geomtry.firstMaterial?.diffuse.contents = color//渲染顏色

        let node = SCNNode(geometry: geomtry)//返回一個節點

        return node
    }

4. 編寫line類

//
//  Line.swift
//  LYJRuler
//
//  Created by Liyanjun on 2017/9/25.
//  Copyright © 2017年 liyanjun. All rights reserved.
//

import ARKit
//定義返回的單位
enum LengthUnit {
    case meter, cenitMeter, inch

    var factor: Float{
        switch self {
        case .meter:
            return 1.0
        case .cenitMeter:
            return 100.0
        case .inch:
            return 39.3700787
        }
    }

    var name: String {
        switch self {
        case .meter:
            return "m"
        case .cenitMeter:
            return "cm"
        case .inch:
            return "inch"
        }
    }
}


class Line {

    var color = UIColor.orange //顏色
    var startNode: SCNNode//起點
    var endNode: SCNNode //終點
    var textNode: SCNNode //文本點
    var text: SCNText //文本
    var lineNode: SCNNode? //線的節點

    let sceneView: ARSCNView //場景
    let startVector: SCNVector3 //起點的三維座標
    let unit: LengthUnit //單位

    //初始化
    init(sceneView: ARSCNView, startVector: SCNVector3, unit: LengthUnit ,_ color:UIColor = UIColor.orange) {
        // 創建節點。(開始,結束,線,數字,單位)

        self.sceneView = sceneView
        self.startVector = startVector
        self.unit = unit
        self.color = color

        let dot = SCNSphere(radius: 0.5)//線的點
        dot.firstMaterial?.diffuse.contents = self.color
        dot.firstMaterial?.lightingModel = .constant //不會產生陰影

        dot.firstMaterial?.isDoubleSided = true //兩面都很亮
        //     創建一個圓的兩面都光亮的,正反面都拋光的求
        startNode = SCNNode(geometry: dot)
        startNode.scale = SCNVector3(1/500.0,1/500.0,1/500.0) // 注意 這裏有坑 巨坑!!! 必須是帶小數點
        startNode.position = startVector

        sceneView.scene.rootNode.addChildNode(startNode)

        endNode = SCNNode(geometry: dot)
        endNode.scale = SCNVector3(1/500.0,1/500.0,1/500.0) // 注意 這裏有坑 巨坑!!!  必須是帶小數點

        text = SCNText(string: "", extrusionDepth: 0.1)
        text.font = .systemFont(ofSize: 5)
        text.firstMaterial?.diffuse.contents = self.color
        text.firstMaterial?.lightingModel = .constant //不會產生陰影
        text.firstMaterial?.isDoubleSided = true //兩面都光亮
        text.alignmentMode = kCAAlignmentCenter
        text.truncationMode = kCATruncationMiddle // ...
        //        包裝文字的節點
        let textWrapperNode = SCNNode(geometry: text)
        textWrapperNode.eulerAngles = SCNVector3Make(0, .pi, 0) // 讓字失蹤面對我
        textWrapperNode.scale = SCNVector3(1/500.0,1/500.0,1/500.0) // 注意 這裏有坑 巨坑!!! 必須是帶小數點

        textNode = SCNNode()
        textNode.addChildNode(textWrapperNode)

        //      添加約束,把文字節點綁在線的中間位置
        let constraint = SCNLookAtConstraint(target: sceneView.pointOfView)
        //        SCNLookAtConstraint 讓他跟隨我們的目標
        //        永遠面向使用者

        constraint.isGimbalLockEnabled = true // 默認是false

        textNode.constraints = [constraint] // 添加約束

        sceneView.scene.rootNode.addChildNode(textNode)
    }


    func remove(){
        startNode.removeFromParentNode()
        endNode.removeFromParentNode()
        textNode.removeFromParentNode()
        lineNode?.removeFromParentNode()
    }

  //更新線的位置
    func update(to vector: SCNVector3) {
        lineNode?.removeFromParentNode() // 把所有的節點都移除掉

        lineNode = startVector.line(vector: vector, color: self.color)

        sceneView.scene.rootNode.addChildNode(lineNode!)
        //        更新文字
        text.string = distance(to: vector)
        //        設置文字的位置 (放在線的中間)
        textNode.position = SCNVector3((startVector.x + vector.x) / 2.0 , (startVector.y + vector.y) / 2.0 ,(startVector.z + vector.z) / 2.0 )

        //        結束節點的位置
        endNode.position = vector

        if endNode.parent == nil {

            sceneView.scene.rootNode.addChildNode(endNode)
        }


    }

    func distance(to vector: SCNVector3) -> String {

        return String(format:"%0.2f %@", startVector.distance(form: vector)*unit.factor, unit.name)
    }
}

最終代碼

//
//  ViewController.swift
//  LYJRuler
//
//  Created by Liyanjun on 2017/9/24.
//  Copyright © 2017年 liyanjun. All rights reserved.
//

import UIKit
import ARKit
import SceneKit


class ViewController: UIViewController {

    @IBOutlet weak var scenview: ARSCNView!

    @IBOutlet weak var targetImg: UIImageView!
    @IBOutlet weak var infoLalbel: UILabel!
    @IBOutlet weak var stackView: UIStackView!

    var session = ARSession()//session
    var configuration = ARWorldTrackingConfiguration()//監聽


    var isMeasuring = false // 默認是沒有在測量狀態

    var vectorZero = SCNVector3() // 0,0,0
    var vectorStart = SCNVector3()
    var vectorEnd = SCNVector3()
    var lines = [Line]()//可以多個線
    var currentLine: Line?
    var unit = LengthUnit.cenitMeter // 單位默認是cm


    override func viewDidLoad() {
        super.viewDidLoad()

        self.setup()
        // Do any additional setup after loading the view, typically from a nib.


    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        //全局追蹤的方法
        session.run(configuration, options: [.resetTracking,.removeExistingAnchors])

    }



    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        session.pause()
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }



    //mark:setup
    func setup()  {
        scenview.delegate = self

        scenview.session = session

        infoLalbel.text = "初始化中..."
    }

    func scanWorld()  {

        guard let worldPosition = scenview.worldVector(for: view.center) else {
            return
        }

        //     如果一個線都沒有
        if  lines.isEmpty {
            infoLalbel.text = "點擊畫面試試看"
        }

        //    如果現在在測量狀態
        if isMeasuring {


            if  vectorStart == vectorZero {
                vectorStart = worldPosition //  把現在的位置設置爲起始節點
                currentLine = Line(sceneView: scenview, startVector: vectorStart, unit: unit)

            }

            //            設置結束的節點
            vectorEnd = worldPosition
            currentLine?.update(to: vectorEnd)
            infoLalbel.text = currentLine?.distance(to: vectorEnd) ?? "..."
        }
    }


    @IBAction func resetButtonHandler(_ sender: UIButton) {

        for line in lines {
            line.remove()
        }
        lines.removeAll()

    }

    @IBAction func unitButtonHandler(_ sender: UIButton) {
    }

    func reset(){

        vectorStart = SCNVector3()
        vectorEnd = SCNVector3()
    }

    //   點擊屏幕
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        //   如果不在測量狀態
        if  !isMeasuring {
            reset() //
            isMeasuring = true
            targetImg.image = UIImage(named: "GreenTarget")
        } else {
            isMeasuring = false

            if let line = currentLine {
                lines.append(line)
                currentLine = nil
                targetImg.image = UIImage(named: "WhiteTarget")
            }

        }
    }

}



extension ViewController: ARSCNViewDelegate {
    //改變攝像頭的焦點就能調用這個方法
    func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {

        DispatchQueue.main.async {
            self.scanWorld()
        }

    }

    func session(_ session: ARSession, didFailWithError error: Error) {

        infoLalbel.text = "錯誤"
    }

    func sessionWasInterrupted(_ session: ARSession) {

        infoLalbel.text = "中斷~"
    }

    func sessionInterruptionEnded(_ session: ARSession) {

        infoLalbel.text = "結束"
    }

}


效果圖

這裏寫圖片描述

發佈了63 篇原創文章 · 獲贊 31 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章