#はじめに
こんにちは、drama(@1901drama)です!
Oculas Goのスタート画面って未来感ありますよね。(↓こういうの)
引用元:MoguLive
ARで現実空間に合わせても出来ないかな。。と思って作ったものを紹介します。
※記載間違い等あれば教えてください。
出来たもの
動画を空中にいっぱい出して、お金が無くても大画面の気分を味わえるアプリ
※音量注意です。
VIDEO その1 pic.twitter.com/NuWU76381J
— drama (@1901drama) 2019年2月17日
・好きな場所に動画を配置
・動画を削除
・大画面化(70インチくらいまで)
VIDEO その2 pic.twitter.com/qZjQdmARJr
— drama (@1901drama) 2019年2月17日
VIDEO その3 pic.twitter.com/Yx4EhVDFXL
— drama (@1901drama) 2019年2月17日
※尚、一旦アプリを消すと配置場所を復元することは出来ません。
ARKitにはカメラを通して、空間から検出した特徴点(ARanchor)を記録する機能があり、エクポート<=>インポートすることで、その時の空間情報(worldMap)を再現することが出来ます。
ただし、今回のアプリは特徴点(ARanchor)に紐づけてオブジェクトを配置しているのではなく、タッチした場所で検出した特徴点(ARanchor)の場所にオブジェクト配置しているだけなので、空間情報(WorldMap)の再現性を利用出来るようしていません。
オブジェクトをエクポート<=>インポートして復元しても、デバイスとの相対距離で復元されます。
ARKit2が出る前(2018/6以前)のARKit記事はこの手法でのノード配置が多いので、使い分けに注意してくださいね。
#難しかった点
同じように始める人向けに、開発で特にはまった点を書いておきます。
動画->SCNnodeへの変換
①動画再生できるプレイヤーを作る
②テクスチャにプレイヤーを貼る
③立方体(奥行がとても薄いので平面ぽく見えます)にテクスチャを貼る
という流れがやや大変です。今回は良い参考があったので大分楽出来ました。
ノードを作成する処理
※動画を選択してくる際に使う変数と、サイズバーを使う為の見知らぬ変数も入ってます。
func createVideoNode(size:CGFloat, videoUrl: URL) -> SCNNode {
// AVPlayerを生成する
let avPlayer = AVPlayer(url: videoUrl)
//ループ再生
avPlayer.actionAtItemEnd = AVPlayer.ActionAtItemEnd.none;
NotificationCenter.default.addObserver(self,
selector: #selector(ViewController.didPlayToEnd),
name: NSNotification.Name("AVPlayerItemDidPlayToEndTimeNotification"),
object: avPlayer.currentItem)
// SKSceneを生成する
let skScene = SKScene(size: CGSize(width: CGFloat(1000 * Slide.value), height: CGFloat(1000 * Slide.value)))
// AVPlayerからSKVideoNodeの生成する(サイズはskSceneと同じ大きさ)
let skNode = SKVideoNode(avPlayer: avPlayer)
skNode.position = CGPoint(x: skScene.size.width / 2.0, y: skScene.size.height / 2.0)
skNode.size = skScene.size
skNode.yScale = -1.0 // 座標系を上下逆にする
skNode.play()
skScene.addChild(skNode)
let node = SCNNode()
node.geometry = SCNBox(width: size, height: size, length: 0.01, chamferRadius: 0)
let material = SCNMaterial()
material.diffuse.contents = skScene
node.geometry?.materials = [material]
node.scale = SCNVector3(1.66 * Slide.value, 1 * Slide.value, 0.01)
return node
}
ワールドマップへの変換
ARanchorに紐づけてオブジェクトを表示するのではなく、ARanchorを検知した場所にオブジェクトを配置するので、
カメラ座標/ワールド座標/相対位置あたりを頭の中で整理して開発するのが少し難しかったです。Unity使えばオブジェクト見えるし、もう少し簡単だったかも。
ノードを配置するあたりの処理
guard let touch = touches.first else {return}
let location:CGPoint = touch.location(in: sceneView)
let hitTest = sceneView.hitTest(location, types: [.featurePoint,.existingPlaneUsingExtent])
if !hitTest.isEmpty {
print("ARhitTest OK")
let videoNode = createVideoNode(size: (CGFloat(3.0 * Slide.value)), videoUrl: self.videoURL_after!)
let hitTestResult = hitTest.first!
let transform = hitTestResult.worldTransform
let thirdColumn = transform.columns.3
let position = SCNVector3(Float(thirdColumn.x) ,Float(thirdColumn.y) ,-0.99)
videoNode.position = sceneView.unprojectPoint(position)
if let camera = sceneView.pointOfView {
let rotation_y = camera.rotation.y
videoNode.rotation = SCNVector4Make(0, 1, 0, rotation_y)
}
//↓動画をタッチして消す処理に使っています。
videoNode.name = "addvideo"
sceneView.scene.rootNode.addChildNode(videoNode)
}else{
print("error ARhitTest.isEmpty")
}
#まとめ
簡単なアプリなので、ARに興味があれば是非皆さんもお試しください!
上記の参考リンクを元にベースを作成し、別途 以下の処理を追加していくと作れます。
・カメラロールから動画を選択
・選択した任意の動画を、シーンに反映させる
・タッチした場所(空間)に、オブジェクトを配置する
・空間にあるオブジェクトを選択する
・削除直前に重力判定を追加する
ここまで読んで頂きありがとうございました!