実装する内容
実装する内容はいたってシンプル。
-
平面検出し、床となる3Dオブジェクトを追加する
-
球体の3Dオブジェクトを床の上に落とす
の2点のみです。
平面検出や、ARSCNViewDelegateのデリゲートメソッドなど基本的なARKitの機能の説明は本記事では省略します。
床となるPlaneNodeクラスを作成
床となるPlaneNode(SCNNode)クラスを作成する際に、physicsBodyプロパティに物理情報を追加する事で、物理シュミレーションが可能となります。
このPlaneNodeは球体を受け止める床となりますので、PhysicsBodyのTypeは固定の.staticとします。
PhysicsBodyのType
- Static : 静止物
- Dynamic : 動く
- Kinematic : 単体だと動かないけど、他の物体の影響で動く
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
class PlaneNode: SCNNode {
fileprivate override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError(“init(coder:) has not been implemented”)
}
init(anchor: ARPlaneAnchor) {
super.init()
geometry = SCNPlane(width: CGFloat(anchor.extent.x), height: CGFloat(anchor.extent.z))
let planeMaterial = SCNMaterial()
planeMaterial.diffuse.contents = UIColor.white.withAlphaComponent(0.2)
geometry?.materials = [planeMaterial]
let planeShape = SCNPhysicsShape(geometry: geometry!, options: nil)
let planeBody = SCNPhysicsBody(type: .static, shape: planeShape)
physicsBody = planeBody
let x = CGFloat(anchor.center.x)
let y = CGFloat(anchor.center.y)
let z = CGFloat(anchor.center.z)
position = SCNVector3(x,y,z)
eulerAngles.x = –.pi / 2
}
func update(anchor: ARPlaneAnchor) {
(geometry as! SCNPlane).width = CGFloat(anchor.extent.x)
(geometry as! SCNPlane).height = CGFloat(anchor.extent.z)
let planeShape = SCNPhysicsShape(geometry: geometry!, options: nil)
let planeBody = SCNPhysicsBody(type: .static, shape: planeShape)
physicsBody = planeBody
let x = CGFloat(anchor.center.x)
let y = CGFloat(anchor.center.y)
let z = CGFloat(anchor.center.z)
position = SCNVector3(x,y,z)
}
}
|
ARSCNViewDelegateのデリゲートメソッドであるrenderer(_:didUpdate:for:)で更新された、より正確なARアンカー(ここでは平面情報)を取得した際に、geometryやpositionを更新できる様にupdate(anchor: ARPlaneAnchor)も用意します。
落下させる球体となるBallNodeクラスを作成
球体となるBallNode(SCNNode)クラスにも、物理情報を追加します。
球体は落下させるので、Typeは.dynamicとします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class BallNode: SCNNode {
required init?(coder aDecoder: NSCoder) {
fatalError(“init(coder:) has not been implemented”)
}
override init() {
super.init()
let ballGeometry = SCNSphere(radius: 0.01)
geometry = ballGeometry
let ballMaterial = SCNMaterial()
let ballShape = SCNPhysicsShape(geometry: ballGeometry, options: nil)
let ballBody = SCNPhysicsBody(type: .dynamic, shape: ballShape)
ballBody.restitution = 1.0
physicsBody = ballBody
ballMaterial.diffuse.contents = UIColor.red.withAlphaComponent(1)
geometry?.materials = [ballMaterial]
}
}
|
タップしたARアンカー上の3D座標に球体を落とす機能を実装
まずタップした位置を取得する為に、viewDidLoad()にUITapGestureRecognizerを設定します。
1
2
|
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapView))
sceneView.addGestureRecognizer(tapGesture)
|
hit Test(_: types:)
1
2
|
func hitTest(_ point: CGPoint,
types: ARHitTestResult.ResultType) -> [ARHitTestResult]
|
- パラメータ
- 第1引数はCGPoint(2D座標軸)
- 第2引数はARHitTestResult.ResultType(検出タイプ)
- 戻り値:
- ARHitTestResult型の配列。※カメラから近い順にソートされる
- .worldTransform: simd_float4x4型 (4×4の変換行列)
- ARHitTestResult型の配列。※カメラから近い順にソートされる
※参考
https://developer.apple.com/documentation/arkit/arscnview/2875544-hittest
ARHit Test Result .Result Type
ARHitTestResult.ResultTypeの種類は下記
- featurePoint (特徴点)
- estimatedHorizontalPlane (推定の平面)
- estimatedVerticalPlane (推定の垂直の平面)
- existingPlane (検出した平面 + 平面サイズを考慮しない)
- existingPlaneUsingExtent (検出した平面 + 平面サイズを考慮する)
- existingPlaneUsingGeometry (検出したジオメトリ + 平面サイズとカタチを考慮する)
今回は、平面のサイズを考慮するので、existingPlaneUsingExtentを使用します。
※参考
https://developer.apple.com/documentation/arkit/arhittestresult/resulttype
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@objc func tapView(recognizer: UIGestureRecognizer) {
//タップした2D座標を取得
let view = recognizer.view as! ARSCNView
let touchLocation = recognizer.location(in: view)
//タップ座標にARアンカーがあるか探す
let hitTestResult = sceneView.hitTest(touchLocation, types: .existingPlaneUsingExtent)
if let result = hitTestResult.first {
//タップされた位置のARアンカーの3D座標を取得
let transform = result.worldTransform
let thirdColumn = transform.columns.3
//球体ノードを生成
let ballNode = BallNode()
//平面のARアンカーの10cm上にポジショニングする
ballNode.position = SCNVector3(thirdColumn.x, thirdColumn.y + 0.1, thirdColumn.z)
sceneView.scene.rootNode.addChildNode(ballNode)
}
}
|
実行
特徴量が少ないテーブルで平面検出した為か、テーブルの縁の認識が少し甘いですが、ARKitの世界に重力を持ち込む事が出来ました。
コメント