放置对象之前如何使用 ARKit Scenekit 测量水平面?我想在放置物体之前先建立房间的模型。提前致谢!
提前知道房间的大小是很棘手的。
但是,无法测量任何检测到的飞机的尺寸:
每次检测到水平或垂直表面(假设您已启用它们),都会生成一个 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的更新大小,则需要考虑到这一点。
ARPlaneAnchor
让我们看看如何做到这一点:
首先,我们创建自己的SCNNode Subclass,称为 PlaneNode,即使更新后,它们也会返回平面的大小。
SCNNode Subclass
请注意,尽管我已经创建了一个子类以使其易于重用,但您无需创建一个子类即可达到相同的结果:
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,但是在此示例中,我们仅假设会有一个。
ViewController
因此,我们将创建一个引用 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:中看到类似的内容
consoleLog:
平面宽度= 0.07m
平面高度= 0.15m
平面对齐=可选(“水平”)
希望这足以让您入门...
1 回答
提前知道房间的大小是很棘手的。
但是,无法测量任何检测到的飞机的尺寸:
每次检测到水平或垂直表面(假设您已启用它们),都会生成一个 ARPlaneAnchor:
在以下委托回调中调用此方法:
因此,您可以获取 ARPlaneAnchor 的宽度,如下所示:
但是,此初始解决方案存在一个问题,即 ARPlaneAnchor 通过以下回调进行更新(e.g. 它的大小已更改):
因此,如果您要跟踪
ARPlaneAnchor
的更新大小,则需要考虑到这一点。让我们看看如何做到这一点:
首先,我们创建自己的
SCNNode Subclass
,称为 PlaneNode,即使更新后,它们也会返回平面的大小。请注意,尽管我已经创建了一个子类以使其易于重用,但您无需创建一个子类即可达到相同的结果:
创建了子类之后,我们需要在
ViewController
中使用它。通常您会得到一个以上的ARPlaneAnchor
,但是在此示例中,我们仅假设会有一个。因此,我们将创建一个引用 PlaneNode 的变量:
然后,在 ARSCNViewDelegate 中,我们将像这样创建 PlaneNode:
然后,您所需要做的就是跟踪 PlaneNode e.g 的更新:
如果一切都按计划进行,您应该在
consoleLog:
中看到类似的内容平面宽度= 0.07m
平面高度= 0.15m
平面对齐=可选(“水平”)
希望这足以让您入门...