如何以编程方式移动ARAnchor?

时间:2017-06-06 23:20:47

标签: swift arkit

我正在尝试新的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是一个get-only属性。

我需要一种方法来改变空间中AR对象相对于用户的位置,以保持AR体验不变 - 这意味着,如果移动设备,AR对象将会移动,但也不会被“卡住”在相机上,就像它被简单地画上一样,但移动就像你在走路时看到一个人移动 - 它们正在移动,你正在移动它看起来很自然。

请注意,此问题的范围仅涉及如何在空间中移动与用户相关的对象(ARAnchor),而不是与平面(ARPlaneAnchor)或另一个检测到的表面(ARHitTestResult)相关。

谢谢!

1 个答案:

答案 0 :(得分:35)

您无需移动锚点。 (手势)这不是您正在寻找的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会自动更新与xScale相关联的每个精灵的yScalezRotationARAnchor,从而提供3D视角的幻觉。但这仅适用于附加到锚点的节点,如上所述,锚点是静态的。

但是,您可以向场景中添加其他节点,并使用这些相同的属性使这些节点与ARSKView - 受管节点匹配。这里有一些代码可以在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)
}

Here's a video,以便您可以看到此代码的实际效果。你会注意到,只要你不移动相机,幻觉就会起作用 - 对AR来说不是那么好。使用SKAction时,我们无法在动画时调整动画的开始/结束状态,因此球会在其原始(屏幕空间)位置/旋转/比例之间来回反复弹跳。

你可以通过直接制作球来做得更好,但这需要做很多工作。你需要在每一帧(或每个view(_:didUpdate:for:)委托回调):

  1. 在动画的每一端保存基于锚点的节点的更新位置,旋转和缩放值。每次didUpdate回调都需要执行两次,因为每个节点都会收到一次回调。

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

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

  4. 将伪造的3D幻觉装入2D图形工具包中需要付出很多努力 - 因此我对SpriteKit的评论并不是进入ARKit的第一步。

    如果您想要AR叠加的3D定位和动画,那么使用3D图形工具包会容易得多。这是前一个示例的重复,但改为使用SceneKit。从ARKit / SceneKit Xcode模板开始,取出宇宙飞船,并将上面的touchesBegan函数粘贴到ViewController。 (也将as ARSKView演员阵容更改为as ARSCNView。)

    然后,一些用于放置2D广告牌sprite的快速代码,通过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版本短很多。 Here's how it looks in action.

    因为我们开始使用3D,我们实际上是在两个3D位置之间制作动画 - 与SpriteKit版本不同,动画保持原样。 (并且没有额外的工作来直接插值和动画属性。)