测量水平面 ARKIT
Measure horizontal plane surface ARKIT
如何在放置物体之前使用ARKit Scenekit测量水平面?我想在放置物体之前先有一个房间模型。提前致谢!
提前知道房间的大小很棘手...
然而,测量任何检测到的平面的大小不是:
每次检测到水平或垂直表面(假设您启用了它们)时,都会生成一个 ARPlaneAnchor:
When you run a world-tracking AR session whose planeDetection option is enabled, the session automatically adds to its list of anchors an ARPlaneAnchor object for each flat surface ARKit detects with the back-facing camera. Each plane anchor provides information about the estimated position and shape of the surface.
这在以下委托回调中调用:
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 通过以下回调得到更新(例如,它的大小发生变化):
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) { }
因此,如果您想跟踪 ARPlaneAnchor
的更新大小,您需要考虑这一点。
让我们看看如何做到这一点:
首先我们创建自己的 SCNNode Subclass
称为 PlaneNode,即使在更新时它也会 return 飞机的大小。
请注意,您不需要创建子类来获得相同的结果,尽管我这样做是为了方便重用:
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 的更新,例如:
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
平面对齐 = 可选("Horizontal")
希望这足以让您入门...
如何在放置物体之前使用ARKit Scenekit测量水平面?我想在放置物体之前先有一个房间模型。提前致谢!
提前知道房间的大小很棘手...
然而,测量任何检测到的平面的大小不是:
每次检测到水平或垂直表面(假设您启用了它们)时,都会生成一个 ARPlaneAnchor:
When you run a world-tracking AR session whose planeDetection option is enabled, the session automatically adds to its list of anchors an ARPlaneAnchor object for each flat surface ARKit detects with the back-facing camera. Each plane anchor provides information about the estimated position and shape of the surface.
这在以下委托回调中调用:
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 通过以下回调得到更新(例如,它的大小发生变化):
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) { }
因此,如果您想跟踪 ARPlaneAnchor
的更新大小,您需要考虑这一点。
让我们看看如何做到这一点:
首先我们创建自己的 SCNNode Subclass
称为 PlaneNode,即使在更新时它也会 return 飞机的大小。
请注意,您不需要创建子类来获得相同的结果,尽管我这样做是为了方便重用:
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 的更新,例如:
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
平面对齐 = 可选("Horizontal")
希望这足以让您入门...