S03E03:AR 用户的前方和右方

580 阅读2分钟

说明

在 AR 中,我们有时需要在用户的前方,或者右方(左方)放置一些虚拟物体。但这有时会有困难:用户的方向和相机(也就是手机)有时并不一致。比如,我们开发了一个竖屏版的 AR 应用,但用户在 AR 过程中会把手机横过来使用,这时手机的右侧就会朝上或朝下。

几何

在 AR 中,手机就是相机,而世界坐标的 Y 轴永远是向上的,即与重力方向相反。我们可以利用这点来寻找前方和右方。

前方

在 AR 应用中,手机的正前方就是 cameraNode-Z 方向,我们只需要知道这个方向在世界坐标中方向就行了。

相机 Node 可以用sceneView.pointOfView来获取,而 -Z 方向则是sceneView.pointOfView?.simdWorldFront。当然,我们也可以用sceneView.pointOfView?.simdLocalFront,然后手动调用转换方法 simdConvertVector(_:to:) or simdConvertVector(_:from:)来转换到世界坐标。

但是,还有些时候,我们并不是想单纯获取手机的正前方,我们希望放置在相机的水平前方。这时我们就需要丢弃 Y 坐标,为了更好的控制距离,我们一般会将新向量进行归一化,这样物体就会放置在水平前方一米处。

guard let front = sceneView.pointOfView?.simdWorldFront else {return}
let horizontalFront = normalize(simd_float3(front.x, 0, front.z))

右方

当前方和竖直上方确定后,我们可以利用叉乘,直接得到水平右方(y 为 0 )。

guard let front = sceneView.pointOfView?.simdWorldFront else {return}
let horazontalRight = cross(front, simd_float3(0, 1, 0))

那么如果,我们想让这个右方处于手机同一个水平面上,就需要将计算出的位置的 y 值改为手机的 y 值,或者手机前方 1 米处的 y 值,就可以了:

代码

@IBAction func frontBtnClick(_ sender: UIButton) {
    guard let front = sceneView.pointOfView?.simdWorldFront else {return}
//        let horizontalFront = normalize(simd_float3(front.x, 0, front.z))
    addChildNode(position: front, color: .red)
}
@IBAction func rightBtnClick(_ sender: UIButton) {
    guard let front = sceneView.pointOfView?.simdWorldFront else {return}
    var horazontalRight = cross(front, simd_float3(0, 1, 0))
    horazontalRight.y = front.y
    addChildNode(position: horazontalRight, color: .green)
}

private func addChildNode(position:simd_float3, color:UIColor) {
    let cube = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0)
    cube.firstMaterial?.diffuse.contents = color
    let cubeNode = SCNNode(geometry: cube)
    cubeNode.simdPosition = position
    sceneView.scene.rootNode.addChildNode(cubeNode)
}