[iOS 11][ARKit] 物理衝突を実装して、キューブを落として見る #WWDC2017

1 はじめに

iOS 11で追加されたARKitを利用して、映像の中に検出した平面に、キューブを落下させている動画が、Youtubeに公開されています。

既に、ARKitで平面(水平)を検出できることは、先の記事で確かめて見ました。

今回は、物理衝突の動きを実装した四角いノードを追加して、動作を確認して見たいと思います。

本記事は Apple からベータ版として公開されているドキュメントを情報源としています。 そのため、正式版と異なる情報になる可能性があります。ご留意の上、お読みください。

2 平面上の位置をタップで指定

下記のコードでは、画面上のタップを、UITapGestureRecognizerで検出し、その座標から、hitTest(_:types:)で物理座標を取得しています。

距離判定の時は、types:featurePointを指定して、アンカーに依存せず座標を取得しましたが、今回は、平面のアンカーと交差する点を取得したいので、existingPlaneUsingExtentを指定しています。


参考:[iOS 11][ARKit] 距離の計測について #WWDC2017

func addTapGesture() {
    let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped))
    self.sceneView.addGestureRecognizer(tapRecognizer)
}

@objc func tapped(recognizer: UIGestureRecognizer) {
    let sceneView = recognizer.view as! ARSCNView
    let touchLocation = recognizer.location(in: sceneView)

    let hitTestResult = sceneView.hitTest(touchLocation, types: .existingPlaneUsingExtent)
    if !hitTestResult.isEmpty {
        if let hitResult = hitTestResult.first {
            addBox(hitResult :hitResult)
        }
    }
}

3 キューブの生成

下記のメソッドは、hitTest(_:types:)で取得した、座標を元に、一辺が10cmのキューブを生成してシーンに追加しています。

なお、取得した平面上の座標から、Y軸方向(3次元で上下の方向)に0.2をプラスして、20cm上から落下させるようにしました。

func addBox(hitResult: ARHitTestResult) {
    let boxGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0)
    let material = SCNMaterial()
    material.diffuse.contents = UIImage(named: "block")
    boxGeometry.materials = [material]

    let boxNode = SCNNode(geometry: boxGeometry)
    // タップした位置より20cm上から落下させる
    boxNode.position = SCNVector3(hitResult.worldTransform.columns.3.x, hitResult.worldTransform.columns.3.y + 0.20, hitResult.worldTransform.columns.3.z)

    sceneView.scene.rootNode.addChildNode(boxNode)
}

4 落下

SCNNodeには、physicsBody:SCNPhysicsBodyというプロパティがあり(デフォルトでnil)、これに物理的な形状の情報を設定すると、物理演算の対象になります。

下記のコードは、キューブノードのphysicsBodyを、元々のgeometry(形状情報)と同じもので初期化しています。

type:dynamicにすることで、デフォルトで下方向に重力がかかり、このままでは、シーンに追加した時点で、どんどん下に落下していきます。

boxNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(geometry: boxGeometry, options: [:]))
boxNode.physicsBody?.categoryBitMask = 1

落下してきたキューブが、平面で止まるようにするには、平面の方にも、物理的な形状の情報を追加する必要があります。

下記では、平面のノードに対して、キューブと同じように、geometry(形状情報)からphysicsBodyを初期化しています。なお、今度は、重力で落下しないように、type:static(固定)にしています。

planeNode.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: geometry!, options: nil))
planeNode.physicsBody?.categoryBitMask = 2

これで、20cm上から落下してきたキューブは、平面でバウンドして止まります。 ただし、落下時のバウンドや、他のキューブとぶつかって、平面ノードからはみ出すと、重力により下に落下することとなり、そこが、平面ノードの検出されたエリア外である場合、見えなくなるまで奈落の底に落ちて行きます。ちょっと悲しいです。

5 最後に

今回は、物理演算を使用して、ARKitで検出した平面に、生成したノードを落下させてみました。 キューブにサイコロのテクスチャを貼れば、これだけで、机の上でサイコロが転がせそうです。

試したコードは、下記に置きました。不明な点があればご参照ください。
github [GitHub] https://github.com/furuya02/ARKitPhyscsDetectionSample

6 参考リンク


Introducing ARKit
Apple Developer > Documentation > ARKit
WWDC2017 Session 602 Introducing ARKit: Augmented Reality for iOS
ARKit by Example — Part 3: Adding geometry and physics fun
[iOS 11] はじめてのARKit #WWDC2017
[iOS 11][ARKit] 距離の計測について #WWDC2017
[iOS 11][ARKit] 平面の検出について #WWDC2017