web-dev-qa-db-ja.com

プログラムで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ミリ秒ごとに(または何でも)繰り返されます。

変換は取得専用のプロパティであるため、上記は機能しません。

ARエクスペリエンスをそのまま維持する方法で、ユーザーに対する空間内のARオブジェクトの位置を変更する方法が必要です。つまり、デバイスを移動すると、ARオブジェクトは移動しますが、「動かなくなる」こともありません。 「それは単に描かれているようにカメラに向かっていますが、人が歩いている間に人が動くのを見るように動きます-彼らは動いていて、動いていて、それは自然に見えます。

この質問の範囲は、平面(ARPlaneAnchor)または別の検出された表面(ARHitTestResult)との関係ではなく、ユーザー(ARAnchor)との関係で空間内のオブジェクトを移動する方法にのみ関係することに注意してください。

ありがとう!

13
Ryan Pfister

アンカーを移動する必要はありません。 (ハンドウェーブ)それはあなたが探しているAPIではありません...

ARAnchorオブジェクトをセッションに追加することは、後で参照できるように、実際の空間でポイントに「ラベルを付ける」ことについて効果的です。点_(1,1,1)_(たとえば)は常に点_(1,1,1)_です—点_(1,1,1)_ではないため、他の場所に移動することはできません。

2Dの類似点を作成するには:アンカーは、ビューの境界のような参照ポイントです。システム(またはコードの別の部分)は、境界の位置をビューに通知し、ビューはそれらの境界を基準にしてコンテンツを描画します。 ARのアンカーは、3Dでコンテンツを描画するために使用できる参照ポイントを提供します。

あなたが求めているのは、仮想コンテンツを2点間で移動(およびアニメーション化)することです。また、ARKit自体は、仮想コンテンツを表示またはアニメーション化するためのものではありません。優れたグラフィックエンジンがたくさんあるので、ARKitはそのホイールを再発明する必要はありません。 ARKitが行うことは、SceneKitやSpriteKit(またはUnityやUnreal、またはMetalやGLで構築されたカスタムエンジン)などの既存のグラフィックステクノロジーを使用してコンテンツを表示またはアニメーション化するための実際の参照フレームを提供することです。


SpriteKitを使用してこれを行おうとしていると述べたので、注意してください。 SpriteKitは2Dエンジンであり、ARSKViewはそこに3次元を追加するいくつかの方法を提供しますが、それらの方法には限界があります。

ARSKViewは、xScaleに関連付けられた各スプライトのyScalezRotation、およびARAnchorを自動的に更新し、3Dパースペクティブの錯覚を提供します。ただし、これはアンカーに接続されたノードにのみ適用され、前述のように、アンカーは静的です。

ただし、他のノードをシーンに追加し、同じプロパティを使用して、それらのノードをARSKView管理対象ノードと一致させることができます。これを行うには、ARKit/SpriteKit Xcodeテンプレートプロジェクトで追加/置換できるコードをいくつか示します。 (アンカーを配置するために最初の2つのタップを使用した後)3番目のタップでバウンスアニメーションを実行するいくつかの基本的なロジックから始めます。

_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. アニメーションの両端にあるアンカーベースのノードの更新された位置、回転、およびスケール値を保存します。ノードごとに1つのコールバックを取得するため、didUpdateコールバックごとにこれを2回実行する必要があります。

  2. 現在の時間に基づいて2つのエンドポイント値の間を補間することにより、アニメーション化されているノードの位置、回転、スケールの値を計算します。

  3. ノードに新しい属性を設定します。 (または、非常に短い期間でこれらの属性にアニメーション化して、1フレームであまりジャンプしないようにすることもできますか?)

これは、2Dグラフィックツールキットに偽の3Dイリュージョンを追加するのに大変な作業です。そのため、SpriteKitについての私のコメントは、ARKitへの最初の大きなステップではありません。


ARオーバーレイの3D配置とアニメーションが必要な場合は、3Dグラフィックツールキットを使用する方がはるかに簡単です。これは前の例の繰り返しですが、代わりにSceneKitを使用しています。 ARKit/SceneKit Xcodeテンプレートから始め、宇宙船を取り出し、同じtouchesBegan関数を上からViewControllerに貼り付けます。 (_as ARSKView_キャストも_as ARSCNView_に変更します。)

次に、2Dビルボードスプライトを配置するためのいくつかの簡単なコードを追加し、ARKit/SpriteKitテンプレートの動作をSceneKitで照合します。

_// 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で作業しているので、実際には2つの3D位置の間でアニメーション化しています。SpriteKitバージョンとは異なり、アニメーションは本来の位置に留まります。 (そして、属性を直接補間およびアニメーション化するための追加の作業なし。)

36
rickster