Skip to content

Commit

Permalink
Fixed and improved samples
Browse files Browse the repository at this point in the history
  • Loading branch information
ThomasGorisse committed Nov 15, 2023
1 parent 01d67ce commit 8dcbfc5
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 90 deletions.
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
package io.github.sceneview.sample.araugmentedimage

import android.graphics.BitmapFactory
import android.media.MediaPlayer
import android.net.Uri
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import com.google.ar.core.TrackingState
import io.github.sceneview.ar.ARSceneView
import io.github.sceneview.ar.arcore.addAugmentedImage
import io.github.sceneview.ar.arcore.getUpdatedAugmentedImages
import io.github.sceneview.ar.arcore.isTracking
import io.github.sceneview.ar.node.AugmentedImageNode
import io.github.sceneview.math.Position
import io.github.sceneview.node.ModelNode
import io.github.sceneview.node.VideoNode

class MainFragment : Fragment(R.layout.fragment_main) {

lateinit var sceneView: ARSceneView

val augmentedImageNodes = mutableListOf<AugmentedImageNode>()

var qrCodeNode: VideoNode? = null
// TODO: Restore when
// var qrCodeNode: VideoNode? = null

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Expand Down Expand Up @@ -55,38 +51,40 @@ class MainFragment : Fragment(R.layout.fragment_main) {
)
)

"qrcode" -> addChildNode(VideoNode(
materialLoader = materialLoader,
player = MediaPlayer().apply {
setDataSource(
requireContext(),
Uri.parse("https://sceneview.github.io/assets/videos/ads/ar_camera_app_ad.mp4")
)
isLooping = true
setOnPreparedListener {
if (augmentedImage.isTracking) {
start()
}
}
prepareAsync()
}
).also { qrCodeNode ->
onTrackingStateChanged = { trackingState ->
when (trackingState) {
TrackingState.TRACKING -> {
if (!qrCodeNode.player.isPlaying) {
qrCodeNode.player.start()
}
}

else -> {
if (qrCodeNode.player.isPlaying) {
qrCodeNode.player.pause()
}
}
}
}
})
"qrcode" -> {}
// TODO: Wait for VideoNode to come back
// addChildNode(VideoNode(
// materialLoader = materialLoader,
// player = MediaPlayer().apply {
// setDataSource(
// requireContext(),
// Uri.parse("https://sceneview.github.io/assets/videos/ads/ar_camera_app_ad.mp4")
// )
// isLooping = true
// setOnPreparedListener {
// if (augmentedImage.isTracking) {
// start()
// }
// }
// prepareAsync()
// }
// ).also { qrCodeNode ->
// onTrackingStateChanged = { trackingState ->
// when (trackingState) {
// TrackingState.TRACKING -> {
// if (!qrCodeNode.player.isPlaying) {
// qrCodeNode.player.start()
// }
// }
//
// else -> {
// if (qrCodeNode.player.isPlaying) {
// qrCodeNode.player.pause()
// }
// }
// }
// }
// })
}
}
addChildNode(augmentedImageNode)
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,51 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.material.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
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.res.stringResource
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
import com.google.ar.core.Frame
import com.google.ar.core.Plane
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.firstByTypeOrNull
import io.github.sceneview.ar.arcore.getUpdatedPlanes
import io.github.sceneview.ar.getDescription
import io.github.sceneview.ar.node.AnchorNode
import io.github.sceneview.math.Position
import io.github.sceneview.ar.rememberARCameraNode
import io.github.sceneview.loaders.MaterialLoader
import io.github.sceneview.loaders.ModelLoader
import io.github.sceneview.model.ModelInstance
import io.github.sceneview.node.CubeNode
import io.github.sceneview.node.ModelNode
import io.github.sceneview.rememberCollisionSystem
import io.github.sceneview.rememberEngine
import io.github.sceneview.rememberMaterialLoader
import io.github.sceneview.rememberModelLoader
import io.github.sceneview.rememberNodes
import io.github.sceneview.sample.Colors
import io.github.sceneview.rememberOnGestureListener
import io.github.sceneview.rememberView
import io.github.sceneview.sample.SceneViewTheme
import kotlinx.coroutines.launch

private const val kModelFile = "https://sceneview.github.io/assets/models/DamagedHelmet.glb"
private const val kModelFile = "models/damaged_helmet.glb"
private const val kMaxModelInstances = 10

class MainActivity : ComponentActivity() {

Expand All @@ -42,73 +62,143 @@ class MainActivity : ComponentActivity() {
Box(
modifier = Modifier.fillMaxSize(),
) {
var isLoading by remember { mutableStateOf(false) }
var planeRenderer by remember { mutableStateOf(true) }
val childNodes = rememberNodes()
val engine = rememberEngine()
val modelLoader = rememberModelLoader(engine)
val childNodes = rememberNodes()
val coroutineScope = rememberCoroutineScope()
val materialLoader = rememberMaterialLoader(engine)
val cameraNode = rememberARCameraNode(engine)
val view = rememberView(engine)
val collisionSystem = rememberCollisionSystem(view)

var planeRenderer by remember { mutableStateOf(true) }

val modelInstances = remember { mutableListOf<ModelInstance>() }
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,
planeRenderer = planeRenderer,
onSessionConfiguration = { session, config ->
collisionSystem = collisionSystem,
sessionConfiguration = { session, config ->
config.depthMode =
when (session.isDepthModeSupported(Config.DepthMode.AUTOMATIC)) {
true -> Config.DepthMode.AUTOMATIC
else -> Config.DepthMode.DISABLED
}
config.instantPlacementMode = Config.InstantPlacementMode.DISABLED
config.instantPlacementMode = Config.InstantPlacementMode.LOCAL_Y_UP
config.lightEstimationMode =
Config.LightEstimationMode.ENVIRONMENTAL_HDR
},
onSessionUpdated = { _, frame ->
if (childNodes.isNotEmpty()) return@ARScene
cameraNode = cameraNode,
planeRenderer = planeRenderer,
onTrackingFailureChanged = {
trackingFailureReason = it
},
onSessionUpdated = { session, updatedFrame ->
frame = updatedFrame

frame.getUpdatedPlanes()
.firstOrNull { it.type == Plane.Type.HORIZONTAL_UPWARD_FACING }
?.let { plane ->
isLoading = true
childNodes += AnchorNode(
engine = engine,
anchor = plane.createAnchor(plane.centerPose)
).apply {
isEditable = true
coroutineScope.launch {
modelLoader.loadModelInstance(kModelFile)?.let {
addChildNode(
ModelNode(
modelInstance = it,
// Scale to fit in a 0.5 meters cube
scaleToUnits = 0.5f,
// Bottom origin instead of center so the
// model base is on floor
centerOrigin = Position(y = -0.5f)
).apply {
isEditable = true
}
)
}
planeRenderer = false
isLoading = false
}
if (childNodes.isEmpty()) {
updatedFrame.getUpdatedPlanes()
.firstOrNull { it.type == Plane.Type.HORIZONTAL_UPWARD_FACING }
?.let { it.createAnchorOrNull(it.centerPose) }?.let { anchor ->
childNodes += createAnchorNode(
engine = engine,
modelLoader = modelLoader,
materialLoader = materialLoader,
modelInstances = modelInstances,
anchor = anchor
)
planeRenderer = false
}
}
},
onGestureListener = rememberOnGestureListener(
onDoubleTap = { motionEvent, node ->
val result1 = cameraNode.hitTest(motionEvent)
val result2 = collisionSystem.hitTest(motionEvent)
println("Sceneview - ray Result1=$result1")
println("Sceneview - ray Result2=$result2")
},
onSingleTapConfirmed = { motionEvent, node ->
if (node == null) {
frame?.hitTest(motionEvent.x, motionEvent.y)
?.firstByTypeOrNull(
planeTypes = setOf(Plane.Type.HORIZONTAL_UPWARD_FACING)
)?.createAnchorOrNull()?.let { anchor ->
childNodes += createAnchorNode(
engine = engine,
modelLoader = modelLoader,
materialLoader = materialLoader,
modelInstances = modelInstances,
anchor = anchor
)
}
}
})
)
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?.let {
it.getDescription(LocalContext.current)
} ?: if (childNodes.isEmpty()) {
stringResource(R.string.point_your_phone_down)
} else {
stringResource(R.string.tap_anywhere_to_add_model)
}
)
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier
.size(64.dp)
.align(Alignment.Center),
color = Colors.Purple700
)
}
}
}
}
}

fun createAnchorNode(
engine: Engine,
modelLoader: ModelLoader,
materialLoader: MaterialLoader,
modelInstances: MutableList<ModelInstance>,
anchor: Anchor
): AnchorNode {
val anchorNode = AnchorNode(engine = engine, anchor = anchor)
val modelNode = ModelNode(
modelInstance = modelInstances.apply {
if (isEmpty()) {
this += modelLoader.createInstancedModel(kModelFile, kMaxModelInstances)
}
}.removeLast(),
// Scale to fit in a 0.5 meters cube
scaleToUnits = 0.5f
).apply {
// Model Node needs to be editable for independent rotation from the anchor rotation
isEditable = true
}
val boundingBoxNode = CubeNode(
engine,
size = modelNode.extents,
center = modelNode.center,
materialInstance = materialLoader.createColorInstance(Color.White.copy(alpha = 0.5f))
).apply {
isVisible = false
}
modelNode.addChildNode(boundingBoxNode)
anchorNode.addChildNode(modelNode)

listOf(modelNode, anchorNode).forEach {
it.onEditingChanged = { editingTransforms ->
boundingBoxNode.isVisible = editingTransforms.isNotEmpty()
}
}
return anchorNode
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<resources>
<string name="app_name">AR Model Viewer Compose</string>
<string name="point_your_phone_down">Point your phone down at an empty space, and move it around slowly</string>
<string name="tap_anywhere_to_add_model">Tap anywhere to add model</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class MainFragment : Fragment(R.layout.fragment_main) {

sceneView = view.findViewById<ARSceneView?>(R.id.sceneView).apply {
planeRenderer.isEnabled = false
onSessionConfiguration = { session, config ->
sessionConfiguration = { session, config ->
config.lightEstimationMode = Config.LightEstimationMode.DISABLED
}
environment = null
Expand Down

0 comments on commit 8dcbfc5

Please sign in to comment.