こんにちは。エクセルソフトの田淵です。
弊社取り扱いの 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 チュートリアル記事まとめ | エクセルソフト ブログ
をご覧ください。
以上です。


