三、ARKit涂鸦①

欢迎小伙伴们的到来~

(一)介绍

如果小伙伴们还没对ARKit有任何的了解可以先看看我前面两章的介绍再来更好的看本章内容

一、ARKit初探索

二、SCNGeometry代码创建几何体

本次的代码已上传 github

先看下我本次分享东西的效果图

1.gif

效果图

(二)理论知识~

下面我们需要对几个概念和类有初步的认识
因为篇幅过长我分到另一篇文章中了,点击查看ARKit代理相关类

(三)开始~

我们要实现在AR场景中绘图,大致我们需要以下几个步骤:

1.检测平面,提示用户在平面内绘图(我们绘制的图像需要放到一个平面上这样看起来会更加真实一些)

2.点击屏幕某个点的获取真实世界对象的位置,如果在一个平面内将手指划过的位置绘制在真实世界中,并实现可以继续绘制某条已经停止绘制的线

3.通过双指向上的手势变成一个有高度的面

下面我们就来实现所有的步骤:

1. 检测平面

我们这里检测平面并用orange颜色的矩形表示出来平面范围

关键代码

//①开启水平面检测并配置到会话
//会话配置开启水平的平面检测(planeDetection默认情况下平面检测关闭的)
let standardConfiguration: ARWorldTrackingConfiguration = {
        let configuration = ARWorldTrackingConfiguration()
        configuration.planeDetection = .horizontal
        return configuration
}()
//用配置启动会话
override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        sceneView.session.run(self.standardConfiguration)
}

//②使用ARSCNViewDelegate检测平面
//一个与新的AR锚点相对应的SceneKit节点已添加到场景中。
/*根据会话配置,ARKit可以自动向会话添加锚点。该视图为每个新锚点调用此方法一次。ARKit还会为调用add(anchor:)方法为ARAnchor使用会话的方法手动添加的任何对象提供可视内容。您可以通过将几何(或其他SceneKit功能)附加到此节点或添加子节点来为锚点提供可视内容。或者,您可以实现renderer(_:nodeFor:)SCNNode方法来为锚点创建自己的节点(或子类的实例)。*/
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {

    if anchor is ARPlaneAnchor {

     //获取ARPlaneAnchor
        let planeAnchor = anchor as! ARPlaneAnchor
        /*当ARKit首先检测到一个ARPlaneAnchor平面时,产生的对象具有一个center值(0,0,0),表示其值的transform位于平面的中心点。
        随着场景分析和平面检测的继续,ARKit可能会确定先前检测到的平面锚点是更大的现实世界表面的一部分,从而增加extent宽度和长度值。平面的新边界可能不会围绕其初始位置对称,所以center点相对于锚(未更改)transform矩阵而改变
        虽然此属性的类型为vector_float3,但平面锚总是二维的,并且总是相对于其transform位置仅在x和z方向上定位和定尺寸。(即,该向量的y分量始终为零)*/
        let extent = planeAnchor.extent//估计的检测平面的宽度和长度
        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检测锚点的方法让ARSCNView帮我们找到平面锚点,然后我们使用画线的方法画了一个矩形来表示检测到的平面范围,以提示用户在此范围内进行绘画

2.在平面上画线

实现步骤

①获取用户触摸事件,并获取到用户本次滑动的两点位置

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] 函数搜索场景中与屏幕上点对应的坐标。


这个方法也可以了解一下 func hitTest(_ point: CGPoint,
types: ARHitTestResult.ResultType) -> [ARHitTestResult]
搜索对应于SceneKit视图中某个点真实世界的对象或AR锚点。

//            ARHitTestResult.ResultType:
//            featurePoint 由ARKit自动识别的点是连续表面的一部分,但没有相应的锚点。
//            estimatedHorizontalPlane 通过搜索(没有相应的锚)检测到的现实平面,其方向垂直于重力。
//            existingPlane 已经在场景中的平面锚(用planeDetection选项检测到),而不考虑平面的大小。
//            existingPlaneUsingExtent 已经在场景中的平面锚(用planeDetection选项检测到),考虑平面的有限大小。
//注:这里我使用existingPlaneUsingExtent为了确定点在一个平面,我只允许在平面上涂鸦
 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)
}

③然后我们把获取的真实世界的两个点的坐标添加到数组中存储并进行绘制

具体查看上传的项目中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.升高面

将绘制的线通过双指向上的手势变成一个有高度的面

利用线的索引数组和当前需要调整的面的高度来添加面的顶点和面的索引, 比如 有 线段 [(0, 0, 0),(1, 0, 0), (2,0,0), (10,0,2), (10,0,3)] 线段索引为[0,1, 1,2, 3,4]。我们两两分割来遍历线段索引, 开始的0,1索引要为面添加四个顶点,而“1,2”因为“1”点的顶点已经在“0,1”时候添加了所以本次添加两个顶点,“3,4”因为“3”顶点没有和“2”顶点相连接所以是新的两个点要为面添加四个顶点

关键代码,具体查看项目updateDrawing方法

//新线段添加四个顶点信息,旧线段添加两个顶点信息
            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)
                    //顶点索引,我这里逆向遍历顶点,两个三角形拼合一个矩形
                    /* 顶点添加顺序1 2
                                 0 3 方便下次有重复点时直接取用 2,3
                     */
                    self._planeIndices += [0+count, 2+count, 1+count,
                                           1+count, 2+count, 3+count]
                    
                    //四个顶点
                    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)
                    //顶点索引
                    self._planeIndices += [0+count, 2+count, 1+count,
                                           1+count, 2+count, 3+count]
                    
                    //添加新的两个顶点
                    let secondVertice = self._lineVertices[secondIndice]
                    self._planeVertices += [secondVertice,
                                            SCNVector3Make(secondVertice.x, secondVertice.y+self._height, secondVertice.z)]
                }
                
                i += 2
            }

本章内容就到这里感谢小伙伴们的到来~
代码已经上传github

你可能感兴趣的:(三、ARKit涂鸦①)