在 ARKit 体验中计算旋转 SCNPlane 的 4 个顶点位置

Calculating the 4 vertices' positions of a rotated SCNPlane in an ARKit experience

我正在开发一个 iOS 应用程序,目前正在尝试获取 AR 体验中旋转 SCNNode 的 4 个顶点的坐标(参考 world/rootNode)

根据我在调试这个乱七八糟的过程中看到的情况,旋转和倾斜的 SCNPlane 的边界框仍然与世界原点水平。

我已经在 this 线程中尝试了解决方案。

我要检测的平面直接放在由 ARKit 检测到的图像之上:

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    guard let imageAnchor = anchor as? ARImageAnchor else { return }
    let referenceImage = imageAnchor.referenceImage

    updateQueue.async {

        // Create a plane to visualize the initial position of the detected image.
        let plane = SCNPlane(width: referenceImage.physicalSize.width,
                             height: referenceImage.physicalSize.height)
        let planeNode = SCNNode(geometry: plane)
        planeNode.opacity = 0.25

        /*
         `SCNPlane` is vertically oriented in its local coordinate space, but
         `ARImageAnchor` assumes the image is horizontal in its local space, so
         rotate the plane to match.
         */
        planeNode.eulerAngles.x = -.pi / 2

        /*
         Image anchors are not tracked after initial detection, so create an
         animation that limits the duration for which the plane visualization appears.
         */
        planeNode.runAction(self.imageHighlightAction)

        // This is where I tried to calculate the edge
        let (min, max) = planeNode.boundingBox

        let topLeft = SCNVector3(min.x, max.y, 0)

        let worldTopLeft = planeNode.convertPosition(topLeft, to: self.sceneView.scene.rootNode)

        let world2dTopLeft = self.sceneView.projectPoint(worldTopLeft)


        node.addChildNode(planeNode)
    }
}

预期的结果是屏幕上的某种 2D 位置,在 3D 视图中将直接位于角的顶部,但我总是得到巨大的数字,例如 x: 8847.291, y: -10651.121

有人解决这个问题吗?

从本地 space 转换到世界 space 时,不应将 z 位置设置为零。您可以使用边界框 minmax 值而不修改它们:

let (min, max) = planeNode.boundingBox

// local space to world space
let minWorld = planeNode.convertPosition(min, to: self.sceneView.scene.rootNode)
let maxWorld = planeNode.convertPosition(max, to: self.sceneView.scene.rootNode)

// world space to screen space
let minScreen = self.sceneView.projectPoint(minWorld)
let maxScreen = self.sceneView.projectPoint(maxWorld)

然后您可以使用 minScreenmaxScreen 的 x 和 y 坐标并忽略 z。

好的,我找到了解决方案:

我需要做一些高等数学来计算这个,你们中的一些人可能比我解释得更好。

工作副本(Swift 4.2)是:

let imageAnchor: ARImageAnchor! // provided by renderer()
let scale: CGFloat!             // UIDevice.main.scale (device scale)

// returns a transform matrix from a point
let imageTransform: (_ x: CGFloat, _ y: CGFloat) -> float4x4 = {x, y in
    var tf = float4x4(diagonal: float4(repeating: 1))
    tf.columns.3 = float4(x: Float(x), y: 0, z: Float(y), w: 1)
    return tf
}

let imageSize = imageAnchor.referenceImage.physicalSize
let worldTransform = imageAnchor.transform

let topLeft = imageTransform(-imageSize.width / 2, -imageSize.height / 2)
let topRight = imageTransform(imageSize.width / 2, -imageSize.height / 2)
let bottomLeft = imageTransform(-imageSize.width / 2, imageSize.height / 2)
let bottomRight = imageTransform(imageSize.width / 2, imageSize.height / 2)

let transformTopLeft = worldTransform * topLeft
let transformTopRight = worldTransform * topRight
let transformBottomLeft = worldTransform * bottomLeft
let transformBottomRight = worldTransform * bottomRight

let pointTopLeft = self.sceneView.projectPoint(SCNVector3(x: transformTopLeft.columns.3.x,
                                                          y: transformTopLeft.columns.3.y,
                                                          z: transformTopLeft.columns.3.z))

let pointTopRight = self.sceneView.projectPoint(SCNVector3(x: transformTopRight.columns.3.x,
                                                           y: transformTopRight.columns.3.y,
                                                           z: transformTopRight.columns.3.z))

let pointBottomLeft = self.sceneView.projectPoint(SCNVector3(x: transformBottomLeft.columns.3.x,
                                                             y: transformBottomLeft.columns.3.y,
                                                             z: transformBottomLeft.columns.3.z))

let pointBottomRight = self.sceneView.projectPoint(SCNVector3(x: transformBottomRight.columns.3.x,
                                                              y: transformBottomLeft.columns.3.y,
                                                              z: transformBottomLeft.columns.3.z))

// Those are the CGPoints, projected to the screen
let cgTL = CGPoint(x: CGFloat(pointTopLeft.x)*scale, y: extent.height-CGFloat(pointTopLeft.y)*scale)
let cgTR = CGPoint(x: CGFloat(pointTopRight.x)*scale, y: extent.height-CGFloat(pointTopRight.y)*scale)
let cgBL = CGPoint(x: CGFloat(pointBottomLeft.x)*scale, y: extent.height-CGFloat(pointBottomLeft.y)*scale)
let cgBR = CGPoint(x: CGFloat(pointBottomRight.x)*scale, y: extent.height-CGFloat(pointBottomRight.y)*scale)

这工作得很好。我希望有人能解释一下我在那里做了什么。