Skip to content

Commit

Permalink
feat: ArScreen
Browse files Browse the repository at this point in the history
  • Loading branch information
AbhigyaKrishna committed Aug 7, 2024
1 parent d91ccbf commit 4695ef5
Show file tree
Hide file tree
Showing 10 changed files with 313 additions and 73 deletions.
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS" />
<uses-permission android:name="android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS" />
<uses-permission android:name="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE" />
<uses-permission android:name="android.permission.CAMERA" />

<application
android:name=".BourbonApplication"
Expand Down
Binary file added app/src/main/assets/Burpee.glb
Binary file not shown.
3 changes: 3 additions & 0 deletions app/src/main/java/me/abhigya/bourbon/BourbonApplication.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package me.abhigya.bourbon

import androidx.multidex.MultiDexApplication
import com.google.android.filament.utils.Utils
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.GlobalContext.startKoin
Expand All @@ -16,6 +17,8 @@ class BourbonApplication : MultiDexApplication() {
androidLogger(Level.ERROR)
modules(modules)
}

Utils.init()
}

}
154 changes: 83 additions & 71 deletions core/src/main/java/me/abhigya/bourbon/core/ui/ar/ArScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,22 @@ package me.abhigya.bourbon.core.ui.ar

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.android.filament.Engine
import com.google.ar.core.Anchor
import com.google.ar.core.Config
Expand All @@ -19,12 +26,10 @@ import com.google.ar.core.TrackingFailureReason
import io.github.sceneview.ar.ARScene
import io.github.sceneview.ar.arcore.createAnchorOrNull
import io.github.sceneview.ar.arcore.isValid
import io.github.sceneview.ar.getDescription
import io.github.sceneview.ar.node.AnchorNode
import io.github.sceneview.ar.rememberARCameraNode
import io.github.sceneview.loaders.MaterialLoader
import io.github.sceneview.loaders.ModelLoader
import io.github.sceneview.math.Rotation
import io.github.sceneview.node.CubeNode
import io.github.sceneview.node.ModelNode
import io.github.sceneview.rememberCollisionSystem
import io.github.sceneview.rememberEngine
Expand All @@ -34,107 +39,114 @@ import io.github.sceneview.rememberNodes
import io.github.sceneview.rememberOnGestureListener
import io.github.sceneview.rememberView
import me.abhigya.bourbon.core.ui.AppScreen
import me.abhigya.bourbon.domain.ModelResource

class ArScreen : AppScreen {
class ArScreen(
private val model: ModelResource
) : AppScreen {

@Composable
override fun invoke() {
Scaffold { paddingValues ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
val engine = rememberEngine()
val modelLoader = rememberModelLoader(engine = engine)
val materialLoader = rememberMaterialLoader(engine = engine)
val cameraNode = rememberARCameraNode(engine = engine)
val view = rememberView(engine = engine)
val collisionSystem = rememberCollisionSystem(view = view)
val childNodes = rememberNodes()
override operator fun invoke() {
Box(modifier = Modifier.fillMaxSize()) {
val engine = rememberEngine()
val modelLoader = rememberModelLoader(engine)
val materialLoader = rememberMaterialLoader(engine)
val cameraNode = rememberARCameraNode(engine)
val view = rememberView(engine)
val collisionSystem = rememberCollisionSystem(view)
val childNodes = rememberNodes()

var planeRenderer by remember { mutableStateOf(true) }

var planeRenderer by remember { mutableStateOf(true) }
var trackingFailureReason by remember { mutableStateOf<TrackingFailureReason?>(null) }
var trackingFailureReason by remember {
mutableStateOf<TrackingFailureReason?>(null)
}

var frame by remember { mutableStateOf<Frame?>(null) }
ARScene(
modifier = Modifier
.fillMaxSize(),
childNodes = childNodes,
engine = engine,
view = view,
modelLoader = modelLoader,
materialLoader = materialLoader,
collisionSystem = collisionSystem,
sessionConfiguration = { session, config ->
config.depthMode = when (session.isDepthModeSupported(Config.DepthMode.AUTOMATIC)) {
var frame by remember { mutableStateOf<Frame?>(null) }
ARScene(
modifier = Modifier.fillMaxSize(),
childNodes = childNodes,
engine = engine,
view = view,
modelLoader = modelLoader,
materialLoader = materialLoader,
collisionSystem = collisionSystem,
sessionConfiguration = { session, config ->
config.depthMode =
when (session.isDepthModeSupported(Config.DepthMode.AUTOMATIC)) {
true -> Config.DepthMode.AUTOMATIC
false -> Config.DepthMode.DISABLED
}
config.instantPlacementMode = Config.InstantPlacementMode.LOCAL_Y_UP
config.lightEstimationMode = Config.LightEstimationMode.ENVIRONMENTAL_HDR
},
cameraNode = cameraNode,
planeRenderer = planeRenderer,
onTrackingFailureChanged = {
trackingFailureReason = it
},
onSessionUpdated = { _, updatedFrame ->
frame = updatedFrame
},
onGestureListener = rememberOnGestureListener(
onSingleTapConfirmed = { motionEvent, node ->
if (node == null) return@rememberOnGestureListener
val hitResult = frame?.hitTest(motionEvent) ?: return@rememberOnGestureListener
hitResult.firstOrNull {
it.isValid(depthPoint = false, point = false)
config.instantPlacementMode = Config.InstantPlacementMode.LOCAL_Y_UP
config.lightEstimationMode =
Config.LightEstimationMode.ENVIRONMENTAL_HDR
},
cameraNode = cameraNode,
planeRenderer = planeRenderer,
onTrackingFailureChanged = {
trackingFailureReason = it
},
onSessionUpdated = { _, updatedFrame ->
frame = updatedFrame
},
onGestureListener = rememberOnGestureListener(
onSingleTapConfirmed = { motionEvent, node ->
if (node == null) {
val hitResults = frame?.hitTest(motionEvent)
?: return@rememberOnGestureListener
hitResults.firstOrNull {
it.isValid(
depthPoint = false,
point = false
)
}?.createAnchorOrNull()
?.let {
planeRenderer = false
childNodes += it.createAnchorNode(
engine,
modelLoader,
materialLoader
modelLoader
)
}
}
)
}
)
}
)

Text(
modifier = Modifier
.systemBarsPadding()
.fillMaxWidth()
.align(Alignment.TopCenter)
.padding(top = 16.dp, start = 32.dp, end = 32.dp),
textAlign = TextAlign.Center,
fontSize = 28.sp,
color = Color.White,
text = trackingFailureReason?.getDescription(LocalContext.current)
?: if (childNodes.isEmpty()) {
"Point your phone down at an empty space, and move it around slowly"
} else {
"Tap anywhere to add model"
}
)
}
}

private fun Anchor.createAnchorNode(
engine: Engine,
modelLoader: ModelLoader,
materialLoader: MaterialLoader,
): AnchorNode {
val anchorNode = AnchorNode(engine, this)
val modelNode = ModelNode(
modelInstance = modelLoader.createModelInstance("Burpee.fbx"),
scaleToUnits = 0.02f
modelInstance = modelLoader.createModelInstance(model.path),
scaleToUnits = model.dimensionScale
).apply {
isEditable = true
isPositionEditable = true
editableScaleRange = 0.01f..0.05f
rotation = Rotation(0.0f, 90f, 0.0f)
}
val boundingBoxNode = CubeNode(
engine,
size = modelNode.extents,
center = modelNode.center,
materialInstance = materialLoader.createColorInstance(Color.White.copy(alpha = 0.5f))
)

modelNode.addChildNode(boundingBoxNode)
anchorNode.addChildNode(modelNode)

listOf(modelNode, anchorNode).forEach {
it.onEditingChanged = { transforms ->
boundingBoxNode.isVisible = transforms.isNotEmpty()
}
}

return anchorNode
}

Expand Down
Loading

0 comments on commit 4695ef5

Please sign in to comment.