Skip to content

Commit

Permalink
Refactor and new features
Browse files Browse the repository at this point in the history
- Add augmented faces
- Add native scene viewer through ARCore
- Add web scene viewer through Chrome Custom Tabs and official samples
- Extract common AR code into ArActivity
  • Loading branch information
SimonMarquis committed Sep 22, 2019
1 parent 3cab68c commit d219439
Show file tree
Hide file tree
Showing 48 changed files with 963 additions and 293 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@

| Scene | Models | Options | Settings |
|---|---|---|---|
| ![](art/screenshot_scene.png) | ![](art/screenshot_models.png) | ![](art/screenshot_options.png) | ![](art/screenshot_settings.png) |
| ![](art/screenshot_scene.png) | ![](art/screenshot_scene_models.png) | ![](art/screenshot_scene_options.png) | ![](art/screenshot_scene_settings.png) |

| Model chooser | Augmented Face | Native Viewer | Web Viewer |
|---|---|---|---|
| ![](art/screenshot_models_gltf.png) | ![](art/screenshot_faces.png) | ![](art/screenshot_viewer_native.png) | ![](art/screenshot_viewer_web.png) |

### Features

Expand Down
6 changes: 6 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ dependencies {
implementation 'androidx.activity:activity:1.1.0-alpha03'
implementation 'androidx.activity:activity-ktx:1.1.0-alpha03'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.browser:browser:1.2.0-alpha08'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.core:core-ktx:1.2.0-alpha04'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0-alpha05'
Expand Down Expand Up @@ -82,3 +83,8 @@ sceneform.asset('sampledata/chroma_key_video/chroma_key_video.obj',
'sampledata/chroma_key_video/chroma_key_video_material.mat',
'sampledata/chroma_key_video/chroma_key_video.sfa',
'src/main/res/raw/chroma_key_video')

sceneform.asset('sampledata/fox/fox_face.fbx',
'sampledata/fox/fox_face_material.mat',
'sampledata/fox/fox_face.sfa',
'src/main/res/raw/fox_face')
Binary file added app/sampledata/fox/ear_fur.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/sampledata/fox/fox_face.fbx
Binary file not shown.
47 changes: 47 additions & 0 deletions app/sampledata/fox/fox_face.sfa
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
materials: [
{
name: 'nose',
parameters: [
{
texture: 'nose_fur',
},
],
source: 'sampledata/fox/fox_face_material.mat',
},
{
name: 'earMAT',
parameters: [
{
texture: 'ear_fur',
},
],
source: 'sampledata/fox/fox_face_material.mat',
},
],
model: {
attributes: [
'Position',
'TexCoord',
'Orientation',
'BoneIndices',
'BoneWeights',
],
collision: {},
file: 'sampledata/fox/fox_face.fbx',
name: 'fox_face',
},
samplers: [
{
file: 'sampledata/fox/ear_fur.png',
name: 'ear_fur',
pipeline_name: 'ear_fur.png',
},
{
file: 'sampledata/fox/nose_fur.png',
name: 'nose_fur',
pipeline_name: '..\\fox\\nose_fur.png',
},
],
version: '0.54:2',
}
23 changes: 23 additions & 0 deletions app/sampledata/fox/fox_face_material.mat
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
material {
"name" : "Fox Face",

"parameters" : [
{
"type" : "sampler2d",
"name" : "texture"
}
],
"requires" : [
"position",
"uv0"
],
"shadingModel" : "unlit",
"blending" : "transparent"
}
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
material.baseColor = texture(materialParams_texture, getUV0());
material.baseColor.rgb = inverseTonemap(material.baseColor.rgb);
}
}
Binary file added app/sampledata/fox/nose_fur.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 12 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.CAMERA" />

<uses-feature android:name="android.hardware.camera.ar" />

<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
Expand All @@ -15,14 +16,16 @@
android:supportsRtl="true"
android:theme="@style/Theme.App"
tools:ignore="GoogleAppIndexingWarning">

<meta-data
android:name="com.google.ar.core"
android:value="required" />
<meta-data
android:name="com.google.android.ar.API_KEY"
android:value="AIzaSyDR3gYtb0FbQG_5LcvxeKUke5bwgXjQVlI"/>
android:value="AIzaSyDR3gYtb0FbQG_5LcvxeKUke5bwgXjQVlI" />

<activity
android:name=".MainActivity"
android:name=".SceneActivity"
android:launchMode="singleTop"
android:screenOrientation="behind">
<intent-filter>
Expand Down Expand Up @@ -53,8 +56,13 @@
<data android:pathPattern=".*\\..*\\..*\\.gltf" />
<data android:pathPattern=".*\\..*\\..*\\..*\\.gltf" />
</intent-filter>

</activity>

<activity
android:name=".FaceActivity"
android:launchMode="singleTop"
android:screenOrientation="behind" />

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}"
Expand All @@ -64,6 +72,7 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/paths" />
</provider>

</application>

</manifest>
224 changes: 224 additions & 0 deletions app/src/main/java/fr/smarquis/ar_toolbox/ArActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package fr.smarquis.ar_toolbox

import android.Manifest.permission.CAMERA
import android.content.Intent
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.graphics.Color
import android.graphics.drawable.Animatable
import android.media.CamcorderProfile
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES
import android.os.Bundle
import android.view.Gravity
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
import android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
import android.widget.ImageView
import android.widget.Toast
import androidx.annotation.LayoutRes
import androidx.annotation.MenuRes
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ContextThemeWrapper
import androidx.appcompat.widget.PopupMenu
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.app.ActivityCompat
import androidx.core.net.toUri
import androidx.core.view.MenuCompat
import com.google.ar.core.ArCoreApk
import com.google.ar.core.Config
import com.google.ar.core.Session
import com.google.ar.core.exceptions.CameraNotAvailableException
import com.google.ar.core.exceptions.UnavailableException
import com.google.ar.sceneform.ArSceneView
import kotlinx.android.synthetic.main.activity_scene.*

abstract class ArActivity(@LayoutRes contentLayoutId: Int) : AppCompatActivity(contentLayoutId) {

private companion object {
const val REQUEST_CAMERA_PERMISSION = 1
}

private var sessionInitializationFailed: Boolean = false

private var installRequested: Boolean = false

private lateinit var videoRecorder: VideoRecorder

private val arCoreViewerIntent by lazy {
createArCoreViewerIntent(
model = getString(R.string.scene_viewer_native_data).toUri(),
link = getString(R.string.scene_viewer_native_link),
title = null
)
}

override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
videoRecorder = VideoRecorder(this, arSceneView()) { isRecording ->
if (isRecording) {
Toast.makeText(this, R.string.recording, Toast.LENGTH_LONG).show()
}

recordingIndicator()?.apply {
visibility = if (isRecording) View.VISIBLE else View.GONE
(drawable as? Animatable)?.apply { if (isRecording) start() else stop() }
}
}
recordingIndicator()?.setOnClickListener {
videoRecorder.stop()
videoRecorder.export()
}
}

override fun onResume() {
super.onResume()
initArSession()
try {
arSceneView().resume()
} catch (ex: CameraNotAvailableException) {
sessionInitializationFailed = true
}
if (hasCameraPermission() && !installRequested && !sessionInitializationFailed) {
renderNavigationBar()
onArResumed()
}
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (requestCode == REQUEST_CAMERA_PERMISSION && !hasCameraPermission()) {
redirectToApplicationSettings()
}
}

override fun onPause() {
super.onPause()
arSceneView().pause()
}

override fun onDestroy() {
super.onDestroy()
arSceneView().destroy()
videoRecorder.stop()
}

abstract fun arSceneView(): ArSceneView

open fun features() = emptySet<Session.Feature>()

abstract fun config(session: Session): Config

abstract fun recordingIndicator(): ImageView?

open fun onArResumed() {}

private fun hasCameraPermission() = ActivityCompat.checkSelfPermission(this, CAMERA) == PERMISSION_GRANTED

private fun requestCameraPermission() {
if (!hasCameraPermission()) {
requestPermissions(arrayOf(CAMERA), REQUEST_CAMERA_PERMISSION)
}
}

private fun renderNavigationBar() {
if (SDK_INT >= VERSION_CODES.O && resources.configuration.orientation != ORIENTATION_LANDSCAPE) {
window.navigationBarColor = Color.WHITE
window.decorView.systemUiVisibility = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS or SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
}
}

private fun initArSession() {
if (arSceneView().session != null) {
return
}
if (!hasCameraPermission()) {
requestCameraPermission()
return
}
if (sessionInitializationFailed) {
return
}
val sessionException: UnavailableException?
try {
val requestInstall = ArCoreApk.getInstance().requestInstall(this, !installRequested)
if (requestInstall == ArCoreApk.InstallStatus.INSTALL_REQUESTED) {
installRequested = true
return
}
installRequested = false
val session = Session(applicationContext, features())
session.configure(config(session))
arSceneView().setupSession(session)
return
} catch (e: UnavailableException) {
sessionException = e
} catch (e: Exception) {
sessionException = UnavailableException().apply { initCause(e) }
}
sessionInitializationFailed = true
Toast.makeText(this, sessionException.message(), Toast.LENGTH_LONG).show()
finish()
}

fun initPopupMenu(anchor: View, @MenuRes menu: Int = 0, onClick: (MenuItem) -> Boolean = { false }, onUpdate: Menu.() -> Unit = {}): PopupMenu {
val popupMenu = PopupMenu(ContextThemeWrapper(this, R.style.PopupMenu), anchor, Gravity.END)
popupMenu.inflate(R.menu.menu_ar)
menu.takeIf { it != 0 }?.let { popupMenu.inflate(it) }
MenuCompat.setGroupDividerEnabled(popupMenu.menu, true)
popupMenu.setOnMenuItemClickListener {
when (it?.itemId) {
R.id.menu_item_screenshot -> arSceneView.screenshot()
R.id.menu_item_quality_2160p -> videoRecorder.start(CamcorderProfile.get(CamcorderProfile.QUALITY_2160P))
R.id.menu_item_quality_1080p -> videoRecorder.start(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P))
R.id.menu_item_quality_720p -> videoRecorder.start(CamcorderProfile.get(CamcorderProfile.QUALITY_720P))
R.id.menu_item_quality_480p -> videoRecorder.start(CamcorderProfile.get(CamcorderProfile.QUALITY_480P))
R.id.menu_item_mode_scene -> {
it.isChecked = true
if (this !is SceneActivity) {
startActivity(Intent(this, SceneActivity::class.java))
finish()
}
return@setOnMenuItemClickListener false
}
R.id.menu_item_mode_faces -> {
it.isChecked = true
if (this !is FaceActivity) {
startActivity(Intent(this, FaceActivity::class.java))
finish()
}
return@setOnMenuItemClickListener false
}
R.id.menu_item_mode_native_viewer -> {
it.isChecked = true
arCoreViewerIntent.safeStartActivity(this)
}
R.id.menu_item_mode_web_viewer -> {
it.isChecked = true
CustomTabsIntent.Builder().build().launchUrl(this, getString(R.string.scene_viewer_web).toUri())
}
else -> return@setOnMenuItemClickListener onClick(it)
}
return@setOnMenuItemClickListener true
}
anchor.setOnClickListener {
popupMenu.menu.apply {
findItem(R.id.menu_item_mode_scene).isChecked = false
findItem(R.id.menu_item_mode_faces).isChecked = false
if (this@ArActivity is SceneActivity) findItem(R.id.menu_item_mode_scene).isChecked = true
if (this@ArActivity is FaceActivity) findItem(R.id.menu_item_mode_faces).isChecked = true
findItem(R.id.menu_item_mode_native_viewer).isEnabled = arCoreViewerIntent.resolveActivity(packageManager) != null
findItem(R.id.menu_item_record).isEnabled = !videoRecorder.isRecording
findItem(R.id.menu_item_quality_2160p).isEnabled = CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_2160P)
findItem(R.id.menu_item_quality_1080p).isEnabled = CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)
findItem(R.id.menu_item_quality_720p).isEnabled = CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)
findItem(R.id.menu_item_quality_480p).isEnabled = CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)
onUpdate(this)
}
popupMenu.show()
}
return popupMenu
}

}
Loading

0 comments on commit d219439

Please sign in to comment.