web-dev-qa-db-ja.com

SCNCameraの現在の向きの前にSceneKitオブジェクトを配置します

ユーザーが画面をタップしたときに新しいSceneKitノードを作成し、設定した距離でカメラの前に直接表示するようにします。テストの場合、これはSCNTextが「ここをタップしました」と読み取ります。また、視線に対して直角にする必要があります-つまり、カメラに「対面」します。

したがって、self.camera.orientation SCNVector4(または同様の)、どうすればよいですか:

  1. 特定の距離でカメラの「前」にノードを配置するSCNVector3を生成しますか?
  2. ノードをカメラに「向ける」ように方向付けるSCNVector4(またはSCNQuaternion)を生成しますか?

(2)に対する答えは、「self.camera.orientation?」と同じだと思いますか?しかし、(1)私は少し失われました。

this one のような似たような質問がいくつかありますが、答えはありません。

12
Maury Markowitz

(Swift 4)

ちょっと、オブジェクトを別のノード(カメラノードなど)に対して特定の位置に配置し、参照ノードと同じ向きにしたい場合は、この単純な関数を使用できます。

func updatePositionAndOrientationOf(_ node: SCNNode, withPosition position: SCNVector3, relativeTo referenceNode: SCNNode) {
    let referenceNodeTransform = matrix_float4x4(referenceNode.transform)

    // Setup a translation matrix with the desired position
    var translationMatrix = matrix_identity_float4x4
    translationMatrix.columns.3.x = position.x
    translationMatrix.columns.3.y = position.y
    translationMatrix.columns.3.z = position.z

    // Combine the configured translation matrix with the referenceNode's transform to get the desired position AND orientation
    let updatedTransform = matrix_multiply(referenceNodeTransform, translationMatrix)
    node.transform = SCNMatrix4(updatedTransform)
}

特定の「cameraNode」の直前に「node」を2m配置したい場合は、次のように呼び出します。

let position = SCNVector3(x: 0, y: 0, z: -2)
updatePositionAndOrientationOf(node, withPosition: position, relativeTo: cameraNode)

編集:カメラノードの取得

カメラノードを取得するには、SCNKit、ARKit、または他のフレームワークを使用しているかどうかによって異なります。以下は、ARKitとSceneKitの例です。

ARKitを使用すると、カメラコンテンツに重なるSCNSceneの3DオブジェクトをレンダリングするARSCNViewがあります。 ARSCNViewのpointOfViewプロパティからカメラノードを取得できます。

let cameraNode = sceneView.pointOfView

SceneKitには、SCNSceneの3DオブジェクトをレンダリングするSCNViewがあります。カメラノードを作成し、好きな場所に配置できるので、次のようにします。

let scnScene = SCNScene()
// (Configure scnScene here if necessary)
scnView.scene = scnScene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 5, 10)  // For example
scnScene.rootNode.addChildNode(cameraNode)

カメラノードをセットアップしたら、ARKitと同じ方法で現在のカメラにアクセスできます。

let cameraNode = scnView.pointOfView
12
Pablo

これほど多くの数学は必要ありません。SceneKitがすでに効果的に実行しているのはすべて数学なので、正しい結果を求めるだけです。

[どうすれば]指定された距離でカメラの「前」にノードを配置するSCNVector3を生成しますか?

カメラをホストするノードのローカル座標系では、「正面」は-Z方向です。カメラに対して(0, 0, -10)に何かを置くと、カメラの視線の中央に10単位離れたところに直接置かれます。それを行うには2つの方法があります。

  1. テキストノードをカメラの子ノードにするだけです。

    cameraNode.addChildNode(textNode)
    textNode.position = SCNVector3(x: 0, y: 0, z: -10)
    

    これにより、カメラが動くことにも注意してくださいwithカメラ:カメラの向きや位置が変わると、textNodeが乗って来ます。 (逆にセルフィースティックを想像してください。電話の一端を回転させ、もう一方の端をそれと一緒に回転させます。)

    テキストノードはカメラノードスペースにあるため、おそらくカメラに向かわせるためにこれ以上何も必要ありません。 (親スペースを基準とするSCNTextのデフォルトの向きは、テキストをカメラに向けます。)

  2. (iOS 11/macOS 10.13/Xcode 9 /などの新機能)SceneKitには、より少ないステップでさまざまな相対幾何学​​計算を行うための便利なアクセサーとメソッドが含まれています。これらのプロパティとメソッドのほとんどは、SCNベクトル/マトリックスタイプを使用する代わりに、システム全体のSIMDライブラリをサポートする新しいフレーバーで利用できます。SIMDライブラリには、オーバーロードオペレーターが組み込まれています。一発ギャグ:

    textNode.simdPosition = camera.simdWorldFront * distance
    

    simdWorldFrontは長さ1.0のベクトルなので、距離を変更するだけで距離を変更できます。)

  3. IOS 10/macOS 10.12 /などを引き続きサポートしている場合、ノード階層を使用して座標変換を行うことができます。テキストノードはシーンの子(実際にはシーンのrootNode)にすることができますが、必要なカメラ空間座標に対応するシーン座標空間位置を見つけることができます。あなたは本当にそれのための関数を必要としませんが、これはトリックをするかもしれません:

    func sceneSpacePosition(inFrontOf node: SCNNode, atDistance distance: Float) -> SCNVector3 {
        let localPosition = SCNVector3(x: 0, y: 0, z: CGFloat(-distance))
        let scenePosition = node.convertPosition(localPosition, to: nil) 
             // to: nil is automatically scene space
        return scenePosition
    }
    textNode.position = sceneSpacePosition(inFrontOf: camera, atDistance: 3)
    

    この場合、テキストノードは最初はカメラに対して相対的に配置されますが、カメラに「接続」されたままにはなりません。カメラを移動しても、ノードは現在の場所にとどまります。テキストノードはシーン空間にあるため、その向きもカメラに接続されていません。

[どうすれば]ノードをカメラに「向ける」ように方向付けるSCNVector4(またはSCNQuaternion)を生成しますか?

この部分は、最初に使用した質問の回答に応じて、あまり処理する必要がないか、SceneKitに完全に引き渡すことができます。

  1. 上記(1)を実行した場合(テキストノードをカメラノードの子にする)、カメラに面するようにするのは1ステップです。そして、先ほど述べたように、テストジオメトリとしてSCNTextを使用すると、そのステップは既に完了しています。ローカル座標空間では、SCNTextは直立しており、デフォルトのカメラの向きから読み取り可能です(つまり、+ Zを上に、+ Xを右にして、-Z方向を向いています)。

    他のジオメトリをカメラに向けたい場合は、一度だけ向きを合わせる必要がありますが、適切な向きに調整する必要があります。たとえば、SCNPyramidのポイントは+ Y方向にあるため、ポイントをカメラに向ける(気をつけて、シャープになる)場合は、X軸の周りにポイントを回転させます。 .pi/2。 (eulerAnglesrotationorientation、またはpivotを使用してこれを行うことができます。)

    カメラの子であるため、指定した回転はカメラに対して相対的であるため、カメラが移動してもカメラに向けられたままになります。

  2. 上記の(2)を実行した場合、couldは、カメラに面するために必要な回転をシーン空間で計算し、ノードまたはカメラが移動するたびに適用します。ただし、それを行うためのAPIがあります。実際には、2つのAPI:SCNLookAtConstraintはノードを他のノードに「向ける」ようにします(制約されたノードの-Z方向によって決定されます)。SCNBillboardConstraintは同じものですが、いくつかの追加他のノードだけでなく、具体的にはカメラに直面するという前提の考慮事項。

    デフォルトのオプションだけでうまく機能します:

    textNode.constraints = [ SCNBillboardConstraint() ]
    

余分な警告

上記のsceneSpacePosition関数を使用しており、カメラノードの向きがレンダリング時に設定されている場合、つまりtransformeulerAnglesrotationを設定しない場合、またはorientationを直接使用しますが、代わりに制約、アクション、アニメーション、または物理学を使用してカメラを移動/方向付けします。考慮すべき点がもう1つあります。

SceneKitには、実際には常に2つのノード階層があります。「モデル」ツリーと「プレゼンテーション」ツリーです。モデルツリーは、transform(など)を直接設定するものです。ノードを移動/方向付ける他のすべての方法は、プレゼンテーションツリーを使用します。したがって、カメラ相対空間を使用して位置を計算する場合、カメラのプレゼンテーションノードが必要になります。

textNode.position = sceneSpacePosition(inFrontOf: camera.presentation, atDistance: 3)
                                                        ~~~~~~~~~~~~~

(なぜこのようになっているのでしょうか?特に、トランザクション内でpositionを設定すると、暗黙のアニメーションを作成できます。これにより、古い位置から新しい位置にアニメーションします。そのアニメーション中、モデルpositionは設定したものであり、プレゼンテーションノードはそのpositionを継続的に変更しています。

28
rickster

そして、私は他のいくつかの答えをつなぎ合わせることでそれを手に入れました...これらの方法から始めましょう:

func pointInFrontOfPoint(point: SCNVector3, direction: SCNVector3, distance: Float) -> SCNVector3 {
    var x = Float()
    var y = Float()
    var z = Float()

    x = point.x + distance * direction.x
    y = point.y + distance * direction.y
    z = point.z + distance * direction.z

    let result = SCNVector3Make(x, y, z)
    return result
}

func calculateCameraDirection(cameraNode: SCNNode) -> SCNVector3 {
    let x = -cameraNode.rotation.x
    let y = -cameraNode.rotation.y
    let z = -cameraNode.rotation.z
    let w = cameraNode.rotation.w
    let cameraRotationMatrix = GLKMatrix3Make(cos(w) + pow(x, 2) * (1 - cos(w)),
                                              x * y * (1 - cos(w)) - z * sin(w),
                                              x * z * (1 - cos(w)) + y*sin(w),

                                              y*x*(1-cos(w)) + z*sin(w),
                                              cos(w) + pow(y, 2) * (1 - cos(w)),
                                              y*z*(1-cos(w)) - x*sin(w),

                                              z*x*(1 - cos(w)) - y*sin(w),
                                              z*y*(1 - cos(w)) + x*sin(w),
                                              cos(w) + pow(z, 2) * ( 1 - cos(w)))

    let cameraDirection = GLKMatrix3MultiplyVector3(cameraRotationMatrix, GLKVector3Make(0.0, 0.0, -1.0))
    return SCNVector3FromGLKVector3(cameraDirection)
}

これで、次のように呼び出すことができます。

let rectnode = SCNNode(geometry: rectangle)
let dir = calculateCameraDirection(cameraNode: self.camera)
let pos = pointInFrontOfPoint(point: self.camera.position, direction:dir, distance: 18)
rectnode.position = pos
rectnode.orientation = self.camera.orientation
sceneView.scene?.rootNode.addChildNode(rectnode)

距離18は、半径10の球体のグリッドと25の背景画像の間に配置します。本当に良く見え、私の5秒のパフォーマンスは驚くほど良いです。

13
Maury Markowitz

簡単にする:

let constraint = SCNTransformConstraint(inWorldSpace: true) { node, transform -> SCNMatrix4 in
    let distance: Float = 1.0

    let newNode = SCNNode()
    let worldFront = self.sceneView!.pointOfView!.simdWorldFront * distance

    newNode.transform = self.sceneView!.pointOfView!.transform
    newNode.transform = SCNMatrix4Translate(newNode.transform, worldFront.x, worldFront.y, worldFront.z)

    return newNode.transform
}
0
Maciej Stramski