[iOS 11][ARKit] 物理衝突を実装して、キューブを落として見る #WWDC2017
1 はじめに
iOS 11で追加されたARKitを利用して、映像の中に検出した平面に、キューブを落下させている動画が、Youtubeに公開されています。
既に、ARKitで平面(水平)を検出できることは、先の記事で確かめて見ました。
今回は、物理衝突の動きを実装した四角いノードを追加して、動作を確認して見たいと思います。
本記事は Apple からベータ版として公開されているドキュメントを情報源としています。 そのため、正式版と異なる情報になる可能性があります。ご留意の上、お読みください。
2 平面上の位置をタップで指定
下記のコードでは、画面上のタップを、UITapGestureRecognizerで検出し、その座標から、hitTest(_:types:)で物理座標を取得しています。
距離判定の時は、types:にfeaturePointを指定して、アンカーに依存せず座標を取得しましたが、今回は、平面のアンカーと交差する点を取得したいので、existingPlaneUsingExtentを指定しています。
参考:[iOS 11][ARKit] 距離の計測について #WWDC2017
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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上から落下させるようにしました。
1 2 3 4 5 6 7 8 9 10 11 12 | 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にすることで、デフォルトで下方向に重力がかかり、このままでは、シーンに追加した時点で、どんどん下に落下していきます。
1 2 | boxNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(geometry: boxGeometry, options: [:])) boxNode.physicsBody?.categoryBitMask = 1 |
落下してきたキューブが、平面で止まるようにするには、平面の方にも、物理的な形状の情報を追加する必要があります。
下記では、平面のノードに対して、キューブと同じように、geometry(形状情報)からphysicsBodyを初期化しています。なお、今度は、重力で落下しないように、type:をstatic(固定)にしています。
1 2 | planeNode.physicsBody = SCNPhysicsBody(type: . static , shape: SCNPhysicsShape(geometry: geometry!, options: nil)) planeNode.physicsBody?.categoryBitMask = 2 |
これで、20cm上から落下してきたキューブは、平面でバウンドして止まります。 ただし、落下時のバウンドや、他のキューブとぶつかって、平面ノードからはみ出すと、重力により下に落下することとなり、そこが、平面ノードの検出されたエリア外である場合、見えなくなるまで奈落の底に落ちて行きます。ちょっと悲しいです。
5 最後に
今回は、物理演算を使用して、ARKitで検出した平面に、生成したノードを落下させてみました。 キューブにサイコロのテクスチャを貼れば、これだけで、机の上でサイコロが転がせそうです。
試したコードは、下記に置きました。不明な点があればご参照ください。
[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