Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package io.github.chayanforyou.arfurniture.ui.screens
- import androidx.compose.foundation.layout.Box
- import androidx.compose.foundation.layout.fillMaxSize
- import androidx.compose.runtime.Composable
- import androidx.compose.runtime.LaunchedEffect
- import androidx.compose.runtime.getValue
- import androidx.compose.runtime.mutableStateOf
- import androidx.compose.runtime.remember
- import androidx.compose.runtime.setValue
- import androidx.compose.ui.Modifier
- import com.google.android.filament.Engine
- import com.google.ar.core.CameraConfig
- import com.google.ar.core.CameraConfigFilter
- import com.google.ar.core.Config
- import com.google.ar.core.Config.PlaneFindingMode
- import com.google.ar.core.Frame
- import com.google.ar.core.Pose
- import com.google.ar.core.Session
- import com.google.ar.core.TrackingFailureReason
- import io.github.sceneview.ar.ARScene
- import io.github.sceneview.ar.ARSceneView
- import io.github.sceneview.ar.arcore.createAnchorOrNull
- import io.github.sceneview.ar.arcore.getUpdatedPlanes
- import io.github.sceneview.ar.arcore.position
- import io.github.sceneview.ar.node.AnchorNode
- import io.github.sceneview.math.Position
- import io.github.sceneview.math.Rotation
- import io.github.sceneview.math.Scale
- import io.github.sceneview.node.ModelNode
- import io.github.sceneview.rememberEngine
- import io.github.sceneview.rememberModelLoader
- import io.github.sceneview.rememberNodes
- import io.github.sceneview.rememberOnGestureListener
- import io.github.sceneview.rememberView
- import java.util.EnumSet
- @Composable
- internal fun ARView(modelName: String) {
- // The destroy calls are automatically made when their disposable effect leaves
- // the composition or its key changes.
- val engine = rememberEngine()
- val modelLoader = rememberModelLoader(engine)
- val childNodes = rememberNodes()
- val view = rememberView(engine)
- // Temporary workaround to prevent crashes upon destruction of ARSceneView.
- // Check https://github.com/SceneView/sceneview-android/issues/450 for more details.
- val cameraNode = remember { ARSceneView.createARCameraNode(engine) }
- // Currently loaded model
- var modelNode by remember { mutableStateOf<ModelNode?>(null) }
- // Whether to show detected plane or not
- var planeRenderer by remember { mutableStateOf(true) }
- // Reason for plane detection failure
- var trackingFailureReason by remember {
- mutableStateOf<TrackingFailureReason?>(null)
- }
- var frame by remember { mutableStateOf<Frame?>(null) }
- var animateModel by remember { mutableStateOf(false) }
- var showCoachingOverlay by remember { mutableStateOf(true) }
- var showGestureOverlay by remember { mutableStateOf(false) }
- LaunchedEffect(key1 = modelName) {
- val path = "models/sofa.glb"
- modelNode = modelLoader.createModelInstance(path).let { instance ->
- ModelNode(
- modelInstance = instance,
- ).apply {
- // Change models initial scale. Currently using width
- scale = Scale(0.3F / size.x)
- // Reset model's initial rotation
- rotation = Rotation()
- // Enable custom gestures on model
- enableGestures()
- }
- }
- }
- Box(modifier = Modifier.fillMaxSize()) {
- ARScene(
- modifier = Modifier.fillMaxSize(),
- cameraNode = cameraNode,
- childNodes = childNodes,
- engine = engine,
- view = view,
- modelLoader = modelLoader,
- onGestureListener = rememberOnGestureListener(
- onSingleTapConfirmed = { _, _ ->
- if (animateModel) {
- animateModel = false
- showGestureOverlay = true
- }
- },
- onMove = { _, event, node ->
- if (node == null) return@rememberOnGestureListener
- // Move gesture to move model node. Instead of changing model position
- // change anchor node position (parent of model node)
- frame?.hitTest(event)?.firstOrNull()?.hitPose?.position?.let { position ->
- val anchor = (node.parent as? AnchorNode) ?: node
- anchor.worldPosition = position
- }
- }
- ),
- sessionCameraConfig = { session -> session.disableDepthConfig() },
- sessionConfiguration = { _, config -> config.configure() },
- planeRenderer = planeRenderer,
- onTrackingFailureChanged = {
- showCoachingOverlay = it != null
- trackingFailureReason = it
- },
- onSessionUpdated = { session, updatedFrame ->
- frame = updatedFrame
- // If no model is placed then create anchor node and add
- // model node to that anchor
- if (childNodes.isEmpty()) {
- updatedFrame.createCenterAnchorNode(engine)?.let { anchorNode ->
- modelNode?.let {
- animateModel = true
- anchorNode.addChildNode(it)
- childNodes.add(anchorNode)
- planeRenderer = false
- showCoachingOverlay = false
- }
- }
- } else if (animateModel) {
- (childNodes.firstOrNull() as? AnchorNode)?.apply {
- val (x, y) = view.viewport.run {
- width / 2F to height / 2F
- }
- val newPosition = updatedFrame.getPose(x, y)?.position ?: return@ARScene
- worldPosition = Position(newPosition.x, newPosition.y, newPosition.z)
- }
- }
- },
- )
- }
- }
- /**
- * Disable Depth API
- * Depth API may give better results. But, some devices (tested on Samsung S20+)
- * creates too much lag. Reason is unknown. Looks like the image acquired
- * with `frame.acquireCameraImage()` is not realised. so we won't be able to acquire
- * new frames when the rate limit is exceeded.
- */
- private fun Session.disableDepthConfig(): CameraConfig {
- val filter = CameraConfigFilter(this)
- filter.setDepthSensorUsage(EnumSet.of(CameraConfig.DepthSensorUsage.DO_NOT_USE))
- val cameraConfigList = getSupportedCameraConfigs(filter)
- return cameraConfigList.first()
- }
- /**
- * Configurations for ARSession
- */
- private fun Config.configure() {
- depthMode = Config.DepthMode.DISABLED
- updateMode = Config.UpdateMode.LATEST_CAMERA_IMAGE
- instantPlacementMode = Config.InstantPlacementMode.LOCAL_Y_UP
- planeFindingMode = PlaneFindingMode.HORIZONTAL
- lightEstimationMode = Config.LightEstimationMode.ENVIRONMENTAL_HDR
- }
- /**
- * Create anchor node at the center of the screen
- */
- private fun Frame.createCenterAnchorNode(engine: Engine): AnchorNode? =
- getUpdatedPlanes().firstOrNull()
- ?.let { it.createAnchorOrNull(it.centerPose) }
- ?.let { anchor ->
- AnchorNode(engine, anchor).apply {
- isEditable = false
- isPositionEditable = false
- updateAnchorPose = false
- }
- }
- /**
- * Get real world position from the given [x] & [y] coordinates.
- */
- private fun Frame.getPose(x: Float, y: Float): Pose? =
- hitTest(x, y).firstOrNull()?.hitPose
- /**
- * Enable gestures for receiver [ModelNode].
- *
- * The default gestures for scale has exponential effect.
- * So using custom gesture for scaling is better.
- */
- private fun ModelNode.enableGestures() {
- var previousScale = 0F
- onScale = { _, _, value ->
- scale = Scale(previousScale * value)
- false
- }
- onScaleBegin = { _, _ ->
- previousScale = scale.x
- false
- }
- onScaleEnd = { _, _ ->
- previousScale = 0F
- }
- // Set rendering priority higher to properly load occlusion.
- setPriority(7)
- // Model Node needs to be editable for independent rotation from the anchor rotation
- isEditable = true
- isScaleEditable = true
- isRotationEditable = true
- editableScaleRange = Float.MIN_VALUE..Float.MAX_VALUE
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement