こんにちは。エクセルソフトの田淵です。
弊社取り扱いの Kudan AR SDK のエントリーです。
SDK のダウンロードは こちら からお申込みください。SDK を使った開発と、個人開発者のリリースは無料でご利用いただけます。企業の方は有料になりますので、@ytabuchi までご連絡ください。
前回の Android のエントリーではマーカーの上に動画を表示させるところまでをやりました。
今回は遂にマーカレスです。
現時点でのサンプルコードは ytabuchi の Github にアップしてあります。この後もコミット追加していくのでスナップショットです。
作業の流れ
今までは最初の MainActivity
で作業していたので、アクティビティを分けたいと思います。
こんな感じですね。
- 今までのマーカーの処理を
MarkerActivity
に移動する - マーカーレスの処理を
ArbiActivity
に新規に作成する MainActivity
は図のようにアクティビティに移動するだけのアクティビティにする
が作業内容です。ちょっと長いです。
また、今回使用する画像一式は、こちら からダウンロードしてください。
マーカー処理を MarkerActivity に移動
最初は既存のコードを移動しましょう。
activity_marker.xml を作成
メイン画面から「Marker」ボタンをタップした後で遷移するページを用意します。
Layout で右クリックして、「New>Layout Resource file」でアクティビティを作成します。
内容は前回までで作成した activity_main.xml
から移行しましょう。
以下のようになります。
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MarkerActivity"> <Button android:id="@+id/showImageButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginStart="16dp" android:onClick="showImageButtonClicked" android:text="Image" app:layout_constraintBottom_toTopOf="@+id/clearButton" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" /> <Button android:id="@+id/showModelButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginLeft="8dp" android:onClick="showModelButtonClicked" android:text="3D Model" app:layout_constraintBottom_toTopOf="@+id/clearButton" app:layout_constraintLeft_toRightOf="@+id/showImageButton" /> <Button android:id="@+id/clearButton" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:onClick="clearAllButtonClicked" android:text="Clear" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> <Button android:id="@+id/showVideoButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginLeft="8dp" android:onClick="showVideoButtonClicked" android:text="Video" app:layout_constraintBottom_toTopOf="@+id/clearButton" app:layout_constraintLeft_toRightOf="@+id/showModelButton" /> </android.support.constraint.ConstraintLayout>
MarkerActivity を作成
次に MarkerActivity
を作成して、MainActivity
にあったキーをセットする部分と permissionsRequest
メソッドを残して、その他を移行していきます。
class MarkerActivity : ARActivity() { private lateinit var imageTrackable: ARImageTrackable private lateinit var videoNode: ARVideoNode override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_marker) } override fun setup() { addImageTrackable() addImageNode() addModelNode() addVideoNode() } private fun addImageTrackable(){ // ARImageTrackable をインスタンス化して画像を読み込み imageTrackable = ARImageTrackable("Lego") imageTrackable.loadFromAsset("lego.jpg") // ARImageTracker のインスタンスを取得して初期化 val trackableManager = ARImageTracker.getInstance() trackableManager.initialise() // imageTrackable を ARImageTracker に追加 trackableManager.addTrackable(imageTrackable) } private fun addImageNode(){ // ARImageNode を画像を指定して初期化 val imageNode = ARImageNode("cow.png") // imageNode のサイズを Trackable のサイズに合わせる val textureMaterial = imageNode.material as ARTextureMaterial val scale = imageTrackable.width / textureMaterial.texture.width imageNode.scaleByUniform(scale) // imageNode を trackable の world に追加 imageTrackable.world.addChild(imageNode) // 初期状態で非表示 imageNode.visible = false } private fun addModelNode(){ // モデルのインポート val modelImporter = ARModelImporter() modelImporter.loadFromAsset("ben.jet") val modelNode = modelImporter.node as ARModelNode // モデルのテクスチャーを読み込み val texture2D = ARTexture2D() texture2D.loadFromAsset("bigBenTexture.png") // ARLightMaterial を作成してテクスチャーとアンビエントを指定 val material = ARLightMaterial() material.setTexture(texture2D) material.setAmbient(0.8f, 0.8f, 0.8f) // モデルの全 meshNode に material を追加 modelImporter.meshNodes.forEach { meshNode -> meshNode.material = material } // modelNode の向きと大きさを指定 modelNode.rotateByDegrees(90f, 1f, 0f, 0f) modelNode.scaleByUniform(0.25f) // modelNode を trackable の world に追加 imageTrackable.world.addChild(modelNode) // 初期状態で非表示 modelNode.visible = false } private fun addVideoNode(){ // ARVideoTexture を mp4 ファイルで初期化 val videoTexture = ARVideoTexture() videoTexture.loadFromAsset("waves.mp4") // ARVideoTexture で ARVideoNode をインスタンス化 videoNode = ARVideoNode(videoTexture) // videoNode のサイズを Trackable のサイズに合わせる val scale = imageTrackable.width / videoTexture.width videoNode.scaleByUniform(scale) // videoNode を trackable の world に追加 imageTrackable.world.addChild(videoNode) // 初期状態で非表示 videoNode.visible = false } // 全ての Node を非表示 private fun hideAll(){ val nodes = imageTrackable.world.children for (node in nodes) node.visible = false } fun clearAllButtonClicked(view: View){ hideAll() } fun showImageButtonClicked(view: View){ hideAll() imageTrackable.world.children[0].visible = true } fun showModelButtonClicked(view: View){ hideAll() imageTrackable.world.children[1].visible = true } fun showVideoButtonClicked(view: View){ hideAll() videoNode.videoTexture.reset() videoNode.videoTexture.start() imageTrackable.world.children[2].visible = true } }
アクティビティを追加したら、AndroidManifest.xml
の application
内に以下の activity
を追加しましょう。
<activity android:name=".MarkerActivity" android:configChanges="orientation|screenSize" android:screenOrientation="fullSensor"> </activity>
configChanges
と screenOrientation
はアクティビティ毎に書くべきなのかいまいち分かっていませんが、これで動作はします()。
マーカーレスの処理を ArbiActivity に追加
コードが別れたので、マーカーレス用の画面と処理を追加していきます。
activity_arbi.xml を作成
先ほどと同様に、Layout を右クリックして、activity_arbi.xml
を作成します。
画面下にオブジェクトを表示するためのボタンを作成したいので、以下のように記述します。
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/changeTrackingModeButton" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:onClick="changeTrackingModeButtonClicked" android:text="Start Tracking" android:textAllCaps="false" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> </android.support.constraint.ConstraintLayout>
ArbiActivity を作成
次に同様に ARActivity
を継承した ArbiActivity
を作成します。
ARActivity
を継承しているので、setup
の override
が必要です。
override fun setup() { super.setup() }
そのまま setup
内で呼び出される、画像を表示するノードを作成します。trackingNode
は後で使用するのでクラス変数 private lateinit var trackingNode: ARImageNode
として定義しています。
private fun addTrackingNode() { // トラッキング(表示)するノードを用意 trackingNode = ARImageNode("CowTracking.png") // ノードの画像を正しい向きにするために回転 trackingNode.rotateByDegrees(90.0f, 1.0f, 0.0f, 0.0f) trackingNode.rotateByDegrees(180.0f, 0.0f, 1.0f, 0.0f) trackingNode.rotateByDegrees(-90.0f, 0.0f, 0.0f, 1.0f) }
原理はまだ理解できていませんが、画像を読み込むと反転して回転された状態で表示されてしまうので、おまじない的に正しい向きに回転させています。
次にマーカーレスモードの View を作成します。
private fun setupArbiTrack() { // ArbiTrack を初期化 val arbiTrack = ARArbiTrack.getInstance() arbiTrack.initialise() // ジャイロマネージャーを初期化 val gyroPlaceManager = ARGyroPlaceManager.getInstance() gyroPlaceManager.initialise() // ターゲットとして使うノードを用意 val targetNode = ARImageNode("CowTarget.png") // デバイスのジャイロでノードが動くようにノードを ARGyroPlaceManager に追加 gyroPlaceManager.world.addChild(targetNode); // ノードの画像を正しい向きにするために回転し、サイズを調整 targetNode.rotateByDegrees(90.0f, 1.0f, 0.0f, 0.0f) targetNode.rotateByDegrees(180.0f, 0.0f, 1.0f, 0.0f) targetNode.rotateByDegrees(-90.0f, 0.0f, 0.0f, 1.0f) targetNode.scaleByUniform(0.3f); // ARArbiTrack の targetNode に指定 arbiTrack.targetNode = targetNode // ARArbiTrack の world に trackingNode を追加 arbiTrack.world.addChild(trackingNode) }
マーカーレスモードでは、ARArbiTrack
と ARGyroPlaceManager
を使用するため、2つのインスタンスを取得し、初期化しています。
ターゲットを ARGyroPlaceManager
に追加して、最後に ARArbiTrack
の targetNode
プロパティに指定すれば、起動時にターゲットが表示されます。
最後に表示するノード trackingNode
を ARArbiTrack
に追加しています。前回までの記事と同じように、ノードをいくつか作っておけば、指定したノードの表示/非表示を切り替えられるのではないかと思いますので、次回試してみます。
そのままボタンの onClick
イベントの処理を書いていきます。
fun changeTrackingModeButtonClicked(view: View) { val arbiTrack = ARArbiTrack.getInstance() val b = findViewById
ARArbiTrack
のインスタンスに isTracking
のプロパティがあるので、これでボタンのテキストと targetNode
の visible
を制御しています。trackingNode
は、ARArbiTrack
をスタート/ストップすると自動で表示/非表示されます。
さあ!完成です。
ArbiActivity
は次のようになっています。
class ArbiActivity : ARActivity() { private lateinit var trackingNode: ARImageNode override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_arbi) } override fun setup() { super.setup() addTrackingNode() setupArbiTrack() } private fun addTrackingNode() { // トラッキング(表示)するノードを用意 trackingNode = ARImageNode("CowTracking.png") // ノードの画像を正しい向きにするために回転 trackingNode.rotateByDegrees(90.0f, 1.0f, 0.0f, 0.0f) trackingNode.rotateByDegrees(180.0f, 0.0f, 1.0f, 0.0f) trackingNode.rotateByDegrees(-90.0f, 0.0f, 0.0f, 1.0f) } private fun setupArbiTrack() { // ArbiTrack を初期化 val arbiTrack = ARArbiTrack.getInstance() arbiTrack.initialise() // ジャイロマネージャーを初期化 val gyroPlaceManager = ARGyroPlaceManager.getInstance() gyroPlaceManager.initialise() // ターゲットとして使うノードを用意 val targetNode = ARImageNode("CowTarget.png") // デバイスのジャイロでノードが動くようにノードを ARGyroPlaceManager に追加 gyroPlaceManager.world.addChild(targetNode); // ノードの画像を正しい向きにするために回転し、サイズを調整 targetNode.rotateByDegrees(90.0f, 1.0f, 0.0f, 0.0f) targetNode.rotateByDegrees(180.0f, 0.0f, 1.0f, 0.0f) targetNode.rotateByDegrees(-90.0f, 0.0f, 0.0f, 1.0f) targetNode.scaleByUniform(0.3f); // ARArbiTrack の targetNode に指定 arbiTrack.targetNode = targetNode // ARArbiTracker の world に trackingNode を追加 arbiTrack.world.addChild(trackingNode) } fun changeTrackingModeButtonClicked(view: View) { val arbiTrack = ARArbiTrack.getInstance() val b = findViewById
Activity を追加したので、AndroidManifest.xml
への追加も忘れずにやっておきましょう。
<activity android:name=".ArbiActivity" android:configChanges="orientation|screenSize" android:screenOrientation="fullSensor"> </activity>
MainActivity を移動するだけのアクティビティにする
MainActivity
に必要なのは ARAPIKey
に setAPIKey
でキーをセットする部分とパーミッションの処理だけですので、それぞれ次のように修正します。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/markerPageButton" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:onClick="markerPageButtonClicked" android:text="Marker AR" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:textAllCaps="false" /> <Button android:id="@+id/arbiPageButton" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:onClick="arbiPageButtonClicked" android:text="Markerless AR" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/markerPageButton" tools:textAllCaps="false" /> </android.support.constraint.ConstraintLayout>
MainActivity
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val key = ARAPIKey.getInstance() key.setAPIKey("API_KEY") permissionsRequest() } fun markerPageButtonClicked(view: View) { val markerIntent = Intent(this, MarkerActivity::class.java) startActivity(markerIntent) } fun arbiPageButtonClicked(view: View) { val arbiIntent = Intent(this, ArbiActivity::class.java) startActivity(arbiIntent) } // Permission のリクエストを OS 標準の requestPermissions メソッドで行う private fun permissionsRequest(){ if (ContextCompat.checkSelfPermission(this, Manifest.permission.INTERNET) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.INTERNET, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA), 111) } } // ダイアログを表示して、本サンプルアプリの設定画面に遷移する private fun permissionsNotSelected() { val builder = AlertDialog.Builder(this) builder.setTitle("Permissions required") builder.setMessage("Please enable the requested permissions in the app settings in order to use this demo app") builder.setPositiveButton("Set permission", DialogInterface.OnClickListener { dialog, id -> dialog.cancel() val intent = Intent() intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS intent.data = Uri.parse("package:eu.kudan.ar") startActivity(intent) }) val noPermission = builder.create() noPermission.show() } // requestPermissions ダイアログの結果を受け、全て許可されていなければ、permissionsNotSelected を呼び出し override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) when (requestCode) { 111 -> { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED && grantResults[2] == PackageManager.PERMISSION_GRANTED && grantResults[3] == PackageManager.PERMISSION_GRANTED) { // パーミッションが必要な処理 } else { permissionsNotSelected() } } } } }
こんな感じで一度配置したら割とピタッと配置される感じを味わってみてください。
マーカーレス #Kudan pic.twitter.com/OD4IMbviyw
— 田淵 義人@エクセルソフト (@ytabuchi) 2018年9月13日
この後は
次はマーカーモードと同じく、マーカーレスで 3D モデルを配置することにチャレンジしてみたいと思います。
Kudan AR SDK エントリー一覧
Kudan AR SDK チュートリアル記事まとめ | エクセルソフト ブログ
をご覧ください。
以上です。