首页 文章

测量水平面 ARKIT [1]

提问于
浏览
-1

放置对象之前如何使用 ARKit Scenekit 测量水平面?我想在放置物体之前先建立房间的模型。提前致谢!

1 回答

  • 0

    提前知道房间的大小是很棘手的。

    但是,无法测量任何检测到的飞机的尺寸:

    每次检测到水平或垂直表面(假设您已启用它们),都会生成一个 ARPlaneAnchor:

    当您运行启用了 planeDetection 选项的 world-tracking AR 会话时,该会话会自动将 ARKit 用 back-facing 相机检测到的每个平面的 ARPlaneAnchor 对象添加到其锚点列表中。每个平面锚提供有关估计的表面位置和形状的信息。

    在以下委托回调中调用此方法:

    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { }
    

    因此,您可以获取 ARPlaneAnchor 的宽度,如下所示:

    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    
            //1. Get The Current ARPlaneAnchor
            guard let anchor = anchor as? ARPlaneAnchor else { return }
    
            //2. Log The Initial Width & Height
            print("""
                Initial Width = \(anchor.extent.x)
                Initial Height = \(anchor.extent.z)
                """)
    
     }
    

    但是,此初始解决方案存在一个问题,即 ARPlaneAnchor 通过以下回调进行更新(e.g. 它的大小已更改):

    func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) { }
    

    因此,如果您要跟踪ARPlaneAnchor的更新大小,则需要考虑到这一点。

    让我们看看如何做到这一点:

    首先,我们创建自己的SCNNode Subclass,称为 PlaneNode,即使更新后,它们也会返回平面的大小。

    请注意,尽管我已经创建了一个子类以使其易于重用,但您无需创建一个子类即可达到相同的结果:

    class PlaneNode: SCNNode {
    
        let DEFAULT_IMAGE: String = "defaultGrid"
        let NAME: String = "PlaneNode"
        var planeGeometry: SCNPlane
        var planeAnchor: ARPlaneAnchor
    
        var widthInfo: String!
        var heightInfo: String!
        var alignmentInfo: String!
    
        //---------------
        //MARK: LifeCycle
        //---------------
    
        /// Inititialization
        ///
        /// - Parameters:
        ///   - anchor: ARPlaneAnchor
        ///   - node: SCNNode
        ///   - node: Bool
        init(anchor: ARPlaneAnchor, node: SCNNode, image: Bool, identifier: Int, opacity: CGFloat = 0.25){
    
            //1. Create The SCNPlaneGeometry
            self.planeAnchor = anchor
            self.planeGeometry = SCNPlane(width: CGFloat(anchor.extent.x), height: CGFloat(anchor.extent.z))
            let planeNode = SCNNode(geometry: planeGeometry)
    
            super.init()
    
            //2. If The Image Bool Is True We Use The Default Image From The Assets Bundle
            let planeMaterial = SCNMaterial()
    
            if image{
    
                planeMaterial.diffuse.contents = UIImage(named: DEFAULT_IMAGE)
    
            }else{
    
                planeMaterial.diffuse.contents = UIColor.cyan
            }
    
            //3. Set The Geometries Contents
            self.planeGeometry.materials = [planeMaterial]
    
            //4. Set The Position Of The PlaneNode
            planeNode.simdPosition = float3(self.planeAnchor.center.x, 0, self.planeAnchor.center.z)
    
            //5. Rotate It On It's XAxis
            planeNode.eulerAngles.x = -.pi / 2
    
            //6. Set The Opacity Of The Node
            planeNode.opacity = opacity
    
            //7. Add The PlaneNode
            node.addChildNode(planeNode)
    
            //8. Set The Nodes ID
            node.name = "\(NAME) \(identifier)"
    
        }
    
        required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
    
        /// Updates The Size Of The Plane As & When The ARPlaneAnchor Has Been Updated
        ///
        /// - Parameter anchor: ARPlaneAnchor
        func update(_ anchor: ARPlaneAnchor) {
    
            self.planeAnchor = anchor
    
            self.planeGeometry.width = CGFloat(anchor.extent.x)
            self.planeGeometry.height = CGFloat(anchor.extent.z)
    
            self.position = SCNVector3Make(anchor.center.x, 0.01, anchor.center.z)
    
            returnPlaneInfo()
        }
    
        //-----------------------
        //MARK: Plane Information
        //-----------------------
    
        /// Returns The Size Of The ARPlaneAnchor & Its Alignment
        func returnPlaneInfo(){
    
            let widthOfPlane = self.planeAnchor.extent.x
            let heightOfPlane = self.planeAnchor.extent.z
    
            var planeAlignment: String!
    
            switch planeAnchor.alignment {
    
            case .horizontal:
                planeAlignment = "Horizontal"
            case .vertical:
                planeAlignment = "Vertical"
            }
    
            #if DEBUG
            print("""
                Width Of Plane =  \(String(format: "%.2fm", widthOfPlane))
                Height Of Plane =  \(String(format: "%.2fm", heightOfPlane))
                Plane Alignment = \(planeAlignment)
                """)
            #endif
    
            self.widthInfo = String(format: "%.2fm", widthOfPlane)
            self.heightInfo = String(format: "%.2fm", heightOfPlane)
            self.alignmentInfo = planeAlignment
          }
    
        }
    

    创建了子类之后,我们需要在ViewController中使用它。通常您会得到一个以上的ARPlaneAnchor,但是在此示例中,我们仅假设会有一个。

    因此,我们将创建一个引用 PlaneNode 的变量:

    var planeNode:PlaneNode?
    

    然后,在 ARSCNViewDelegate 中,我们将像这样创建 PlaneNode:

    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    
        //1. Get The Current ARPlaneAnchor
        guard let anchor = anchor as? ARPlaneAnchor else { return }
    
        //2. Create The PlaneNode
        if planeNode == nil{
            planeNode = PlaneNode(anchor: anchor, node: node, image: true, identifier: 0, opacity: 1)
            node.addChildNode(planeNode!)
            planeNode?.name = String("Detected Plane")
        }
    
    }
    

    然后,您所需要做的就是跟踪 PlaneNode e.g 的更新:

    func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    
            guard let anchor = anchor as? ARPlaneAnchor, let existingPlane = planeNode else { return }
            existingPlane.update(anchor)
    }
    

    如果一切都按计划进行,您应该在consoleLog:中看到类似的内容

    平面宽度= 0.07m

    平面高度= 0.15m

    平面对齐=可选(“水平”)

    希望这足以让您入门...

相关问题