仕事でARKitを触ることになったのでまとめ
・基礎編はこちら
やったこと
カメラの映像に
・テキストを作成・配置する
・テクスチャを作成・配置する
・タップで動かす
・ドラッグで動かす
・平面を検出する
使ったもの
・ARKit
・SceneKit
コード
テキストを配置する
・backgroundColor出ても最初の一瞬だけ
・SCNViewとARSCNViewは違うらしい->あとで追記
・willAppearでconfig設定してrun & willDisAppearでpauseの流れが鉄板らしい
->平面検出のときはviewDidLoad
ではなくrender(_ didAdd)
に書く
->リアルタイムでテクスチャの座標を更新したい場合とか
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet weak var scnView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
scnView.backgroundColor = UIColor.red
scnView.allowsCameraControl = true //カメラ位置をタップでコントロール可能にする
scnView.scene = scene
let str = "本日は晴天なり"
let depth: CGFloat = 0.5
let text = SCNText(string: str, extrusionDepth: depth)
text.font = UIFont(name: "HiraKakuProN-W6", size: 0.5);
let textNode = SCNNode(geometry: text)
let (min, max) = (textNode.boundingBox)
let x = CGFloat(max.x - min.x)
let y = CGFloat(max.y - min.y)
textNode.position = SCNVector3(-(x/2), -1, -2)
print("\(str) width=\(x)m height=\(y)m depth=\(depth)m")
scnView.scene.rootNode.addChildNode(textNode)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
scnView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
scnView.session.pause()
}
}
テクスチャを作成・配置する
SCNFloorは無限平面
SCNPlaneは有限平面?
ドラッグ・タップで動かす・平面を検出する
・secondSceneViewという名前に特に意味はないです
・namedにはファイル名、inDirectoryにはパスを書きましょう(当たり前のはずなのにここ逆にしてて時間ムッチャ無駄にしたのでメモ)
->ここのsceneはこの情報のSCNSceneが存在するかどうかnilチェックしてるだけ
->viewDidLoadのsceneSecondとは別物
guard let scene = SCNScene(named: "FinalBaseMesh.scn", inDirectory: "art.scnassets") else {fatalError()}
・withNameはidentityのNameにしよう
->Nameはファイル名(.scn)と一緒でないとここでクラッシュする
->GeometoryのNameもこれと同じにすると平面検出ごとにテクスチャが新規追加される
->テクスチャは出るけどなんか一色の塊みたいな感じになってて変(Sampleにあるship.scn
はlightの設定加えたらでた)
secondSceneView.autoenablesDefaultLighting = true
->あとで追記
guard let bearNode = scene.rootNode.childNode(withName: "FinalBaseMesh", recursively: true) else {fatalError()}
import UIKit
import SceneKit
import ARKit
class SecondViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet var secondSceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
let sceneSecond = SCNScene()
secondSceneView.scene = sceneSecond
secondSceneView.delegate = self
secondSceneView.showsStatistics = true
secondSceneView.debugOptions = ARSCNDebugOptions.showFeaturePoints
//tapGesture
secondSceneView.addGestureRecognizer(UITapGestureRecognizer(
target: self, action: #selector(self.tapView(sender:))))
secondSceneView.addGestureRecognizer(UIPanGestureRecognizer(
target: self, action: #selector(self.dragView(sender:))))
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
//検知対象を指定する。現在は水平のみ指定可能。
configuration.planeDetection = .horizontal
//セッションを稼働
secondSceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
//セッションを停止
secondSceneView.session.pause()
}
// MARK: - ARSCNViewDelegate
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
print("\(self.classForCoder)/" + #function)
guard let planeAnchor = anchor as? ARPlaneAnchor else {fatalError()}
//sceneとnodeを読み込み
guard let scene = SCNScene(named: "FinalBaseMesh.scn", inDirectory: "art.scnassets") else {fatalError()}
//identityちゃんと設定
guard let bearNode = scene.rootNode.childNode(withName: "FinalBaseMesh", recursively: true) else {fatalError()}
// nodeのスケールを調整する
let (min, max) = bearNode.boundingBox
let w = CGFloat(max.x - min.x)
// 1mを基準にした縮尺を計算
let magnification = 1.0 / w
bearNode.scale = SCNVector3(magnification, magnification, magnification)
// nodeのポジションを設定
bearNode.position = SCNVector3(planeAnchor.center.x, 0, planeAnchor.center.z)
//作成したノードを追加
DispatchQueue.main.async(execute: {
node.addChildNode(bearNode)
})
}
@objc func tapView(sender: UIGestureRecognizer) {
let tapPoint = sender.location(in: secondSceneView)
let results = secondSceneView.hitTest(tapPoint, types: .existingPlaneUsingExtent)
if !results.isEmpty {
if let result = results.first ,
let anchor = result.anchor ,
let node = secondSceneView.node(for: anchor) {
let action1 = SCNAction.rotateBy(x: CGFloat(-90 * (Float.pi / 180)), y: 0, z: 0, duration: 0.5)
let action2 = SCNAction.wait(duration: 1)
DispatchQueue.main.async(execute: {
node.runAction(
SCNAction.sequence([
action1,
action2,
action1.reversed()
])
)
})
}
}
}
@objc func dragView(sender: UIGestureRecognizer) {
let tapPoint = sender.location(in: secondSceneView)
let results = secondSceneView.hitTest(tapPoint, types: .existingPlane)
if !results.isEmpty {
if let result = results.first ,
let anchor = result.anchor ,
let node = secondSceneView.node(for: anchor) {
DispatchQueue.main.async(execute: {
// 実世界の座標をSCNVector3で返したものを反映
node.position = SCNVector3(result.worldTransform.columns.3.x, result.worldTransform.columns.3.y, result.worldTransform.columns.3.z)
})
}
}
}
}
分からないこと
・ライフサイクルとrenderの違いはなに
->backgroundcolor一瞬しか反映しない
SCNViewだと怒られないことがARSCNViewだと怒られた
->インスタンス書いてなかったから?
Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: 'Could not instantiate class named ARSCNView because no class named ARSCNView was found; the class needs to be defined in source code or linked in from a library (ensure the class is part of the correct target)'
@IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
//let scnView = self.view as! SCNView
sceneView.backgroundColor = UIColor.black
}
参考
・ARKitで簡単ARやってみた - Qiita
・ARKitのはじめかた その1「5分で出来るARアプリ」 - Qiita
・SceneKitのシーンやノードを理解【3次元世界への入り口】 – techpartner