如何以编程方式移动ARAnchor? [英] How do I programmatically move an ARAnchor?

查看:104
本文介绍了如何以编程方式移动ARAnchor?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在试用新的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)无关。

谢谢!

推荐答案

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

ARAnchor对象添加到会话实际上就是给现实世界空间中的一个点贴上标签,以便您以后可以引用它。例如,点(1,1,1)始终是点(1,1,1)-您不能将其移动到其他地方,因为这样它就不再是点(1,1,1)

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

您所问的实际上是关于在两个点之间移动(和动画移动)虚拟内容的问题。而且ARKit本身并不是为了显示或动画虚拟内容--外面有很多很棒的图形引擎,所以ARKit不需要重新发明那个轮子。ARKit所做的是为您提供一个真实的参照系,让您使用现有的图形技术,如SceneKit或SpriteKit(或Unity或Unreale,或使用金属或GL构建的自定义引擎)来显示内容或设置内容的动画。


既然您提到尝试使用SpriteKit执行此操作...小心,它会变得乱七八糟。SpriteKit是一个2D引擎,虽然ARSKView提供了一些将第三维强加于其中的方法,但这些方法有其局限性。

ARSKView自动更新与ARAnchor关联的每个精灵的xScaleyScalezRotation,提供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. 在节点上设置新属性。(或者可以在很短的持续时间内将其设置为这些属性的动画,这样它就不会在一帧中跳跃太多?)

把一个假的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版本短得多。 Here's how it looks in action.

因为我们首先在3D中工作,所以我们实际上是在两个3D位置之间进行动画制作--与SpriteKit版本不同,动画停留在它应该位于的位置。(并且不需要为直接插入属性和设置属性动画而进行额外的工作。)

这篇关于如何以编程方式移动ARAnchor?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆