假设矩形在水平面上,则可以对所有4个角落进行场景命中测试,并使用其中的3个角来计算矩形的宽度、高度、中心和方向。
我在GitHub上提供了一个演示应用程序,正是这样做的:https://github.com/mludowise/ARKitRectangleDetection
VNRectangleObservation
的矩形角坐标将相对于图像大小并且根据手机的旋转而在不同的坐标系中。您需要通过视图大小对它们进行乘法并根据手机的旋转进行反转:
func convertFromCamera(_ point: CGPoint, view sceneView: ARSCNView) -> CGPoint {
let orientation = UIApplication.shared.statusBarOrientation
switch orientation {
case .portrait, .unknown:
return CGPoint(x: point.y * sceneView.frame.width, y: point.x * sceneView.frame.height)
case .landscapeLeft:
return CGPoint(x: (1 - point.x) * sceneView.frame.width, y: point.y * sceneView.frame.height)
case .landscapeRight:
return CGPoint(x: point.x * sceneView.frame.width, y: (1 - point.y) * sceneView.frame.height)
case .portraitUpsideDown:
return CGPoint(x: (1 - point.y) * sceneView.frame.width, y: (1 - point.x) * sceneView.frame.height)
}
}
然后,您可以对所有4个角进行命中测试。在执行命中测试时,使用类型为.existingPlaneUsingExtent
很重要,这样ARKit才会返回水平面的命中结果。
let tl = sceneView.hitTest(convertFromCamera(rectangle.topLeft, view: sceneView), types: .existingPlaneUsingExtent)
let tr = sceneView.hitTest(convertFromCamera(rectangle.topRight, view: sceneView), types: .existingPlaneUsingExtent)
let bl = sceneView.hitTest(convertFromCamera(rectangle.bottomLeft, view: sceneView), types: .existingPlaneUsingExtent)
let br = sceneView.hitTest(convertFromCamera(rectangle.bottomRight, view: sceneView), types: .existingPlaneUsingExtent)
接着就变得有点复杂了...
因为每个命中测试可能会返回0到n个结果,所以你需要过滤掉任何在不同平面上的命中测试。你可以通过比较每个ARHitTestResult
的锚点来完成此操作:
hit1.anchor == hit2.anchor
此外,您只需要识别矩形的3个角落,就可以确定其尺寸、位置和方向,所以如果一个角没有返回任何命中测试结果也没关系。请参阅这里,了解我是如何做到的。
您可以通过左右两个角之间的距离(对于顶部或底部)来计算矩形的宽度。同样地,您可以通过上下两个角之间的距离(对于左侧或右侧)来计算矩形的高度。
func distance(_ a: SCNVector3, from b: SCNVector3) -> CGFloat {
let deltaX = a.x - b.x
let deltaY = a.y - b.y
let deltaZ = a.z - b.z
return CGFloat(sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ))
}
let width = distance(right, from: left)
let height = distance(top, from: bottom)
您可以通过获取矩形对角线上的中点(即topLeft和bottomRight或topRight和bottomLeft)来计算其位置。
let midX = (c1.x + c2.x) / 2
let midY = (c1.y + c2.y) / 2
let midZ = (c1.z + c2.z) / 2
let center = SCNVector3Make(midX, midY, midZ)
您还可以根据左右两个角落(顶部或底部)的位置计算矩形的方向(沿y轴旋转)。
let distX = right.x - left.x
let distZ = right.z - left.z
let orientation = -atan(distZ / distX)
将所有这些放在一起,然后在矩形上叠加显示AR图像。以下是通过子类化SCNNode
来显示虚拟矩形的示例:
class RectangleNode: SCNNode {
init(center: SCNVector3, width: CGFloat, height: CGFloat, orientation: Float) {
super.init()
let planeGeometry = SCNPlane(width: width, height: height)
let rectNode = SCNNode(geometry: planeGeometry)
var transform = SCNMatrix4MakeRotation(-Float.pi / 2.0, 1.0, 0.0, 0.0)
transform = SCNMatrix4Rotate(transform, orientation, 0, 1, 0)
rectNode.transform = transform
self.addChildNode(rectNode)
self.position = center
}
}