26
2017
09

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 = "结束"
    }

}


效果图

这里写图片描述

上一篇:Could not find com.android.support:support-v4:26.0.2 下一篇:使用websocket-bench进行socket.io性能测试