歡迎小伙伴們的到來(lái)~
(一)介紹
如果小伙伴們還沒對(duì)ARKit有任何的了解可以先看看我前面兩章的介紹再來(lái)更好的看本章內(nèi)容
一吸奴、ARKit初探索
二洞坑、SCNGeometry代碼創(chuàng)建幾何體
本次的代碼已上傳 github
先看下我本次分享東西的效果圖
(二)理論知識(shí)~
下面我們需要對(duì)幾個(gè)概念和類有初步的認(rèn)識(shí)
因?yàn)槠^長(zhǎng)我分到另一篇文章中了,點(diǎn)擊查看ARKit代理相關(guān)類
(三)開始~
我們要實(shí)現(xiàn)在AR場(chǎng)景中繪圖拙毫,大致我們需要以下幾個(gè)步驟:
1.檢測(cè)平面,提示用戶在平面內(nèi)繪圖(我們繪制的圖像需要放到一個(gè)平面上這樣看起來(lái)會(huì)更加真實(shí)一些)
2.點(diǎn)擊屏幕某個(gè)點(diǎn)的獲取真實(shí)世界對(duì)象的位置,如果在一個(gè)平面內(nèi)將手指劃過的位置繪制在真實(shí)世界中,并實(shí)現(xiàn)可以繼續(xù)繪制某條已經(jīng)停止繪制的線
3.通過雙指向上的手勢(shì)變成一個(gè)有高度的面
下面我們就來(lái)實(shí)現(xiàn)所有的步驟:
1. 檢測(cè)平面
我們這里檢測(cè)平面并用orange顏色的矩形表示出來(lái)平面范圍
關(guān)鍵代碼
//①開啟水平面檢測(cè)并配置到會(huì)話
//會(huì)話配置開啟水平的平面檢測(cè)(planeDetection默認(rèn)情況下平面檢測(cè)關(guān)閉的)
let standardConfiguration: ARWorldTrackingConfiguration = {
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal
return configuration
}()
//用配置啟動(dòng)會(huì)話
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
sceneView.session.run(self.standardConfiguration)
}
//②使用ARSCNViewDelegate檢測(cè)平面
//一個(gè)與新的AR錨點(diǎn)相對(duì)應(yīng)的SceneKit節(jié)點(diǎn)已添加到場(chǎng)景中依许。
/*根據(jù)會(huì)話配置,ARKit可以自動(dòng)向會(huì)話添加錨點(diǎn)缀蹄。該視圖為每個(gè)新錨點(diǎn)調(diào)用此方法一次峭跳。ARKit還會(huì)為調(diào)用add(anchor:)方法為ARAnchor使用會(huì)話的方法手動(dòng)添加的任何對(duì)象提供可視內(nèi)容。您可以通過將幾何(或其他SceneKit功能)附加到此節(jié)點(diǎn)或添加子節(jié)點(diǎn)來(lái)為錨點(diǎn)提供可視內(nèi)容缺前≈恚或者,您可以實(shí)現(xiàn)renderer(_:nodeFor:)SCNNode方法來(lái)為錨點(diǎn)創(chuàng)建自己的節(jié)點(diǎn)(或子類的實(shí)例)衅码。*/
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
if anchor is ARPlaneAnchor {
//獲取ARPlaneAnchor
let planeAnchor = anchor as! ARPlaneAnchor
/*當(dāng)ARKit首先檢測(cè)到一個(gè)ARPlaneAnchor平面時(shí)拯刁,產(chǎn)生的對(duì)象具有一個(gè)center值(0,0,0),表示其值的transform位于平面的中心點(diǎn)逝段。
隨著場(chǎng)景分析和平面檢測(cè)的繼續(xù)筛璧,ARKit可能會(huì)確定先前檢測(cè)到的平面錨點(diǎn)是更大的現(xiàn)實(shí)世界表面的一部分,從而增加extent寬度和長(zhǎng)度值惹恃。平面的新邊界可能不會(huì)圍繞其初始位置對(duì)稱,所以center點(diǎn)相對(duì)于錨(未更改)transform矩陣而改變
雖然此屬性的類型為vector_float3棺牧,但平面錨總是二維的巫糙,并且總是相對(duì)于其transform位置僅在x和z方向上定位和定尺寸。(即颊乘,該向量的y分量始終為零)*/
let extent = planeAnchor.extent//估計(jì)的檢測(cè)平面的寬度和長(zhǎng)度
let center = planeAnchor.center
//let transform = planeAnchor.transform
//添加平面node
let w_2 = extent.x / 2.0
let h_2 = extent.z / 2.0
let lineSource = SCNGeometrySource(vertices:
[SCNVector3Make(-w_2, 0, h_2),
SCNVector3Make( w_2, 0, h_2),
SCNVector3Make(-w_2, 0, -h_2),
SCNVector3Make( w_2, 0, -h_2)])
let indices:[UInt32] = [0, 1, 1, 3, 3, 2, 2, 0]
let lineElements = SCNGeometryElement(indices: indices, primitiveType: .line)
let line = SCNGeometry(sources: [lineSource], elements: [lineElements])
//渲染器
line.firstMaterial = SCNMaterial()
line.firstMaterial?.diffuse.contents = UIColor.orange
let planeNode = SCNNode(geometry: line)
planeNode.position = SCNVector3(center)
// planeNode.transform = SCNMatrix4(transform)
node.addChildNode(planeNode)
}
}
我們使用ARSCNViewDelegate檢測(cè)錨點(diǎn)的方法讓ARSCNView幫我們找到平面錨點(diǎn)参淹,然后我們使用畫線的方法畫了一個(gè)矩形來(lái)表示檢測(cè)到的平面范圍醉锄,以提示用戶在此范圍內(nèi)進(jìn)行繪畫
2.在平面上畫線
實(shí)現(xiàn)步驟
①獲取用戶觸摸事件,并獲取到用戶本次滑動(dòng)的兩點(diǎn)位置
guard let firstTouch = touches.first else {
return;
}
let location = firstTouch.location(in: self.sceneView)
let pLocation = firstTouch.previousLocation(in: self.sceneView)
②使用sceneView hitTest(_ point: CGPoint,
options: [SCNHitTestOption : Any]? = nil) -> [SCNHitTestResult] 函數(shù)搜索場(chǎng)景中與屏幕上點(diǎn)對(duì)應(yīng)的坐標(biāo)浙值。
這個(gè)方法也可以了解一下 func hitTest(_ point: CGPoint,
types: ARHitTestResult.ResultType) -> [ARHitTestResult]
搜索對(duì)應(yīng)于SceneKit視圖中某個(gè)點(diǎn)真實(shí)世界的對(duì)象或AR錨點(diǎn)恳不。
// ARHitTestResult.ResultType:
// featurePoint 由ARKit自動(dòng)識(shí)別的點(diǎn)是連續(xù)表面的一部分,但沒有相應(yīng)的錨點(diǎn)开呐。
// estimatedHorizontalPlane 通過搜索(沒有相應(yīng)的錨)檢測(cè)到的現(xiàn)實(shí)平面烟勋,其方向垂直于重力。
// existingPlane 已經(jīng)在場(chǎng)景中的平面錨(用planeDetection選項(xiàng)檢測(cè)到)筐付,而不考慮平面的大小卵惦。
// existingPlaneUsingExtent 已經(jīng)在場(chǎng)景中的平面錨(用planeDetection選項(xiàng)檢測(cè)到),考慮平面的有限大小瓦戚。
//注:這里我使用existingPlaneUsingExtent為了確定點(diǎn)在一個(gè)平面沮尿,我只允許在平面上涂鴉
let planeHitTestResults = self.sceneView?.hitTest(point, types: .existingPlaneUsingExtent)
if let result = planeHitTestResults?.first {
let translation = result.worldTransform.columns.3
planeHitTestPosition = SCNVector3Make(translation.x, translation.y, translation.z)
}
③然后我們把獲取的真實(shí)世界的兩個(gè)點(diǎn)的坐標(biāo)添加到數(shù)組中存儲(chǔ)并進(jìn)行繪制
具體查看上傳的項(xiàng)目中MQShapeNode addVertices和updateDrawing方法
let source = SCNGeometrySource(vertices:self._lineVertices)
let elements = SCNGeometryElement(indices: self._lineIndices, primitiveType: .line)
let geometry = SCNGeometry(sources: [source], elements: [elements])
//渲染器
geometry.firstMaterial = SCNMaterial()
geometry.firstMaterial?.diffuse.contents = UIColor.red
self.geometry = geometry
2.升高面
將繪制的線通過雙指向上的手勢(shì)變成一個(gè)有高度的面
利用線的索引數(shù)組和當(dāng)前需要調(diào)整的面的高度來(lái)添加面的頂點(diǎn)和面的索引, 比如 有 線段 [(0, 0, 0),(1, 0, 0), (2,0,0), (10,0,2), (10,0,3)] 線段索引為[0,1, 1,2, 3,4]较解。我們兩兩分割來(lái)遍歷線段索引畜疾, 開始的0,1索引要為面添加四個(gè)頂點(diǎn)印衔,而“1啡捶,2”因?yàn)椤?”點(diǎn)的頂點(diǎn)已經(jīng)在“0,1”時(shí)候添加了所以本次添加兩個(gè)頂點(diǎn)当编,“3届慈,4”因?yàn)椤?”頂點(diǎn)沒有和“2”頂點(diǎn)相連接所以是新的兩個(gè)點(diǎn)要為面添加四個(gè)頂點(diǎn)
關(guān)鍵代碼,具體查看項(xiàng)目updateDrawing方法
//新線段添加四個(gè)頂點(diǎn)信息,舊線段添加兩個(gè)頂點(diǎn)信息
var i = 0
let lineIndicesCount = self._lineIndices.count
while i+1 < lineIndicesCount {
let firstIndice = Int(self._lineIndices[i])
let secondIndice = Int(self._lineIndices[i+1])
//是否是一條新的線段
let isNewLine = (i-1 > 0 && firstIndice == self._lineIndices[i-1]) ? false : true
if isNewLine {
let count = UInt32(self._planeVertices.count)
//頂點(diǎn)索引忿偷,我這里逆向遍歷頂點(diǎn)金顿,兩個(gè)三角形拼合一個(gè)矩形
/* 頂點(diǎn)添加順序1 2
0 3 方便下次有重復(fù)點(diǎn)時(shí)直接取用 2,3
*/
self._planeIndices += [0+count, 2+count, 1+count,
1+count, 2+count, 3+count]
//四個(gè)頂點(diǎn)
let firstVertice = self._lineVertices[firstIndice]
let secondVertice = self._lineVertices[secondIndice]
self._planeVertices += [firstVertice,
SCNVector3Make(firstVertice.x, firstVertice.y+self._height, firstVertice.z),
secondVertice,
SCNVector3Make(secondVertice.x, secondVertice.y+self._height, secondVertice.z)]
} else {
let count = UInt32(self._planeVertices.count-2)
//頂點(diǎn)索引
self._planeIndices += [0+count, 2+count, 1+count,
1+count, 2+count, 3+count]
//添加新的兩個(gè)頂點(diǎn)
let secondVertice = self._lineVertices[secondIndice]
self._planeVertices += [secondVertice,
SCNVector3Make(secondVertice.x, secondVertice.y+self._height, secondVertice.z)]
}
i += 2
}
本章內(nèi)容就到這里感謝小伙伴們的到來(lái)~
代碼已經(jīng)上傳github