AR Advent Calendar 2019 16日目の記事です。
はじめに
ARアプリにおいて、ARコンテンツの表示位置設定は毎回課題となる処理です。
特定のマーカーにカメラを向けるとコンテンツが表示される場合は分かりやすいですが、例えばテーブルや床から平面を検知させオブジェクトを表示したい場合は、ユーザが迷わないような導入部を作る必要があります。
Apple提供サンプルコード Placing Objects and Handling 3D Interaction ではiOS13から追加された ARCoachingOverlayView と独自の FocusSquare クラスを使用し自然な導入インタラクションを実現しています。
自作ARアプリでも利用できるよう、今回はその2つを連動させた最小実装を書いてみます。


最小実装
新規プロジェクト作成
今回はSwift、SceneKitを使用し新規プロジェクトを作成します。

FocusSquareクラス追加
Placing Objects and Handling 3D Interaction から Focus Square
フォルダをコピーしプロジェクトに追加。



ViewController実装
以下をViewControllerに実装。
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController {
@IBOutlet private var sceneView: ARSCNView!
private let coachingOverlay = ARCoachingOverlayView()
private let focusSquare = FocusSquare()
private let updateQueue = DispatchQueue(label: "tokyo.shmdevelopment.serialSceneKitQueue")
// MARK: - View Controller Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
sceneView.showsStatistics = true
sceneView.debugOptions = .showFeaturePoints
setupCoachingOverlay()
sceneView.scene.rootNode.addChildNode(focusSquare)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = [.horizontal]
sceneView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors])
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
// MARK: - Focus Square
private func updateFocusSquare(isObjectVisible: Bool) {
if isObjectVisible || coachingOverlay.isActive {
focusSquare.hide()
} else {
focusSquare.unhide()
}
if let camera = sceneView.session.currentFrame?.camera,
case .normal = camera.trackingState,
let query = sceneView.getRaycastQuery(),
let result = sceneView.castRay(for: query).first {
updateQueue.async {
self.sceneView.scene.rootNode.addChildNode(self.focusSquare)
self.focusSquare.state = .detecting(raycastResult: result, camera: camera)
}
} else {
updateQueue.async {
self.focusSquare.state = .initializing
self.sceneView.pointOfView?.addChildNode(self.focusSquare)
}
}
}
}
extension ViewController: ARSCNViewDelegate {
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
DispatchQueue.main.async {
self.updateFocusSquare(isObjectVisible: false)
}
}
}
extension ViewController: ARCoachingOverlayViewDelegate {
private func setupCoachingOverlay() {
coachingOverlay.session = sceneView.session
coachingOverlay.delegate = self
coachingOverlay.translatesAutoresizingMaskIntoConstraints = false
sceneView.addSubview(coachingOverlay)
NSLayoutConstraint.activate([
coachingOverlay.centerXAnchor.constraint(equalTo: view.centerXAnchor),
coachingOverlay.centerYAnchor.constraint(equalTo: view.centerYAnchor),
coachingOverlay.widthAnchor.constraint(equalTo: view.widthAnchor),
coachingOverlay.heightAnchor.constraint(equalTo: view.heightAnchor)
])
coachingOverlay.goal = .horizontalPlane
}
}
extension ARSCNView {
fileprivate func castRay(for query: ARRaycastQuery) -> [ARRaycastResult] {
return session.raycast(query)
}
fileprivate func getRaycastQuery(for alignment: ARRaycastQuery.TargetAlignment = .any) -> ARRaycastQuery? {
return raycastQuery(from: screenCenter, allowing: .estimatedPlane, alignment: alignment)
}
fileprivate var screenCenter: CGPoint {
return CGPoint(x: bounds.midX, y: bounds.midY)
}
}

さいごに
Safariから3DオブジェクトをAR表示できるようになりました。
ARアイコンをタップするだけでカメラが起動し自然なインタラクションでオブジェクトの表示や操作ができます。



またApple提供サンプルコード SwiftShot では、FocusSquare表示が多機能になっていて、ゲーム盤面のサイズ指定、位置指定、向き調整が可能になっています。

導入部実装は一度作れば使い回せるのでこれを機に自分用の導入インタラクションを作れると良いですね!