1. 搭載初始代碼
這一部分比較簡單就不再秒速了
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])
- 這個方法,是用來來搜索 ARSession 檢測到的maodian還有真是世界的兌現, 不是view 裏 的 SceneKit. 裏 的內容
- 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 = "結束"
}
}