一尘不染

如何以编程方式移动ARAnchor?

swift

我正在尝试使用新的ARKit来替代我拥有的另一个类似解决方案。太棒了!但是我似乎无法弄清楚如何以编程方式移动ARAnchor。我想将锚点缓慢移动到用户的左侧。

将锚点创建在用户前方2米处:

        var translation = matrix_identity_float4x4
        translation.columns.3.z = -2.0
        let transform = simd_mul(currentFrame.camera.transform, translation)

        let anchor = ARAnchor(transform: transform)
        sceneView.session.add(anchor: anchor)

之后,将对象移至用户的左/右(x轴)…

anchor.transform.columns.3.x = anchor.transform.columns.3.x + 0.1

每隔50毫秒(或任何其他时间)重复一次。

上面的方法不起作用,因为transform是仅获取属性。

我需要一种方法来改变AR对象在空间中相对于用户的位置,从而保持AR体验的完好无损-
意思是,如果您移动设备,AR对象将会移动但不会被“卡住”就像简单地画在相机上一样,但移动时就像您在走过时看到一个人在移动-
他们在移动并且您在移动并且看起来很自然。

请注意,此问题的范围仅与如何相对于用户(ARAnchor)在空间中移动物体有关,而与平面(ARPlaneAnchor)或与另一个检测到的曲面(ARHitTestResult)无关。

谢谢!


阅读 268

收藏
2020-07-07

共1个答案

一尘不染

您无需移动锚点。 (手势) 这不是您要查找的API …

ARAnchor对象添加到会话中实际上是关于“标记”现实世界中的点,以便以后可以引用它。点(1,1,1)(例如)始终是点(1,1,1)-您无法将其移动到其他地方,因为那样就不再是该点(1,1,1)

进行2D类比:锚点是参考点,类似于视图的边界。系统(或另一段代码)告诉视图其边界在哪里,视图相对于那些边界绘制其内容。AR中的锚点为您提供了可用于3D绘图内容的参考点。

您要问的实际上是关于在两点之间移动虚拟内容(并对其进行动画处理)。而且,ARKit本身并不是要显示或动画化虚拟内容-
那里有很多很棒的图形引擎,因此ARKit不需要重新发明轮子。ARKit的作用是为您提供一个现实的参考框架,供您使用SceneKit或SpriteKit(或Unity或Unreal,或使用Metal或GL构建的自定义引擎)等现有图形技术显示或设置内容的动画。


既然您提到要尝试使用SpriteKit进行此操作,请注意,它变得凌乱。SpriteKit是一个2D引擎,尽管ARSKView提供了一些方法来解决其中的三维问题,但这些方法有其局限性。

ARSKView自动更新xScaleyScale以及zRotation与相关联的每个精灵的ARAnchor,提供3D立体的错觉。但这仅适用于附加到锚点的节点,并且如上所述,锚点是静态的。

但是,您可以将其他节点添加到场景中,并使用这些相同的属性使这些节点与ARSKView-managed节点匹配。您可以在ARKit / SpriteKit
Xcode模板项目中添加/替换以下代码来执行此操作。我们将从一些基本逻辑开始,在第三个拍子上(在使用前两个拍子放置锚点之后)运行弹跳动画。

var anchors: [ARAnchor] = []
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    // Start bouncing on touch after placing 2 anchors (don't allow more)
    if anchors.count > 1 {
        startBouncing(time: 1)
        return
    }
    // Create anchor using the camera's current position
    guard let sceneView = self.view as? ARSKView else { return }
    if let currentFrame = sceneView.session.currentFrame {

        // Create a transform with a translation of 30 cm in front of the camera
        var translation = matrix_identity_float4x4
        translation.columns.3.z = -0.3
        let transform = simd_mul(currentFrame.camera.transform, translation)

        // Add a new anchor to the session
        let anchor = ARAnchor(transform: transform)
        sceneView.session.add(anchor: anchor)
        anchors.append(anchor)
    }
}

然后,使该动画发生一些SpriteKit的乐趣:

var ballNode: SKLabelNode = {
    let labelNode  = SKLabelNode(text: "🏀")
    labelNode.horizontalAlignmentMode = .center
    labelNode.verticalAlignmentMode = .center
    return labelNode
}()
func startBouncing(time: TimeInterval) {
    guard
        let sceneView = self.view as? ARSKView,
        let first = anchors.first, let start = sceneView.node(for: first),
        let last = anchors.last, let end = sceneView.node(for: last)
        else { return }

    if ballNode.parent == nil {
        addChild(ballNode)
    }
    ballNode.setScale(start.xScale)
    ballNode.zRotation = start.zRotation
    ballNode.position = start.position

    let scale = SKAction.scale(to: end.xScale, duration: time)
    let rotate = SKAction.rotate(toAngle: end.zRotation, duration: time)
    let move = SKAction.move(to: end.position, duration: time)

    let scaleBack = SKAction.scale(to: start.xScale, duration: time)
    let rotateBack = SKAction.rotate(toAngle: start.zRotation, duration: time)
    let moveBack = SKAction.move(to: start.position, duration: time)

    let action = SKAction.repeatForever(.sequence([
        .group([scale, rotate, move]),
        .group([scaleBack, rotateBack, moveBack])
        ]))
    ballNode.removeAllActions()
    ballNode.run(action)
}

这是一个视频,因此您可以看到此代码的实际效果。您会注意到,只有在不移动相机的情况下,这种错觉才会起作用-
对于AR来说并不是那么好。使用时SKAction,我们不能在动画时调整动画的开始/结束状态,因此,球会在其原始(屏幕空间)位置/旋转/比例之间不断来回弹跳。

您可以通过直接对球进行动画处理来做得更好,但这是很多工作。您需要在每个框架(或每个view(_:didUpdate:for:)委托回调)上:

  1. 保存动画两端的基于锚点的节点的更新后的位置,旋转和比例值。每个didUpdate回调您需要两次,因为每个节点都会得到一个回调。

  2. 通过基于当前时间在两个端点值之间进行插值,计算出要设置动画的节点的位置,旋转和缩放值。

  3. 在节点上设置新属性。(或者可以在很短的时间内将其设置为这些属性的动画,这样它在一帧内不会跳得太多吗?)

将虚假的3D幻象刺入2D图形工具箱需要大量工作,因此我对SpriteKit的评论并不是ARKit迈出的重要一步。


如果您希望AR叠加层具有3D定位和动画效果,则使用3D图形工具包要容易得多。这是前面示例的重复,但是使用了SceneKit。从ARKit /
SceneKit Xcode模板开始,取出飞船,然后touchesBegan从上方将相同的功能粘贴到ViewController。(也将as ARSKView演员表更改为as ARSCNView。)

然后,一些快速代码用于放置2D广告牌精灵,并通过SceneKit匹配ARKit / SpriteKit模板的行为:

// in global scope
func makeBillboardNode(image: UIImage) -> SCNNode {
    let plane = SCNPlane(width: 0.1, height: 0.1)
    plane.firstMaterial!.diffuse.contents = image
    let node = SCNNode(geometry: plane)
    node.constraints = [SCNBillboardConstraint()]
    return node
}

// inside ViewController
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    // emoji to image based on https://stackoverflow.com/a/41021662/957768
    let billboard = makeBillboardNode(image: "⛹️".image())
    node.addChildNode(billboard)
}

最后,为弹跳球添加动画:

let ballNode = makeBillboardNode(image: "🏀".image())
func startBouncing(time: TimeInterval) {
    guard
        let sceneView = self.view as? ARSCNView,
        let first = anchors.first, let start = sceneView.node(for: first),
        let last = anchors.last, let end = sceneView.node(for: last)
        else { return }

    if ballNode.parent == nil {
        sceneView.scene.rootNode.addChildNode(ballNode)
    }

    let animation = CABasicAnimation(keyPath: #keyPath(SCNNode.transform))
    animation.fromValue = start.transform
    animation.toValue = end.transform
    animation.duration = time
    animation.autoreverses = true
    animation.repeatCount = .infinity
    ballNode.removeAllAnimations()
    ballNode.addAnimation(animation, forKey: nil)
}

这次,动画代码比SpriteKit版本短很多。 这是实际效果。

因为我们从3D开始工作,所以实际上是在两个3D位置之间进行动画处理-
与SpriteKit版本不同,动画会保留在原本应有的位置。(并且无需进行直接内插和设置属性动画的额外工作。)

2020-07-07