Advertisement
chayanforyou

ARView

Mar 12th, 2025
107
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Kotlin 8.28 KB | None | 0 0
  1. package io.github.chayanforyou.arfurniture.ui.screens
  2.  
  3. import androidx.compose.foundation.layout.Box
  4. import androidx.compose.foundation.layout.fillMaxSize
  5. import androidx.compose.runtime.Composable
  6. import androidx.compose.runtime.LaunchedEffect
  7. import androidx.compose.runtime.getValue
  8. import androidx.compose.runtime.mutableStateOf
  9. import androidx.compose.runtime.remember
  10. import androidx.compose.runtime.setValue
  11. import androidx.compose.ui.Modifier
  12. import com.google.android.filament.Engine
  13. import com.google.ar.core.CameraConfig
  14. import com.google.ar.core.CameraConfigFilter
  15. import com.google.ar.core.Config
  16. import com.google.ar.core.Config.PlaneFindingMode
  17. import com.google.ar.core.Frame
  18. import com.google.ar.core.Pose
  19. import com.google.ar.core.Session
  20. import com.google.ar.core.TrackingFailureReason
  21. import io.github.sceneview.ar.ARScene
  22. import io.github.sceneview.ar.ARSceneView
  23. import io.github.sceneview.ar.arcore.createAnchorOrNull
  24. import io.github.sceneview.ar.arcore.getUpdatedPlanes
  25. import io.github.sceneview.ar.arcore.position
  26. import io.github.sceneview.ar.node.AnchorNode
  27. import io.github.sceneview.math.Position
  28. import io.github.sceneview.math.Rotation
  29. import io.github.sceneview.math.Scale
  30. import io.github.sceneview.node.ModelNode
  31. import io.github.sceneview.rememberEngine
  32. import io.github.sceneview.rememberModelLoader
  33. import io.github.sceneview.rememberNodes
  34. import io.github.sceneview.rememberOnGestureListener
  35. import io.github.sceneview.rememberView
  36. import java.util.EnumSet
  37.  
  38. @Composable
  39. internal fun ARView(modelName: String) {
  40.     // The destroy calls are automatically made when their disposable effect leaves
  41.     // the composition or its key changes.
  42.     val engine = rememberEngine()
  43.     val modelLoader = rememberModelLoader(engine)
  44.     val childNodes = rememberNodes()
  45.     val view = rememberView(engine)
  46.     // Temporary workaround to prevent crashes upon destruction of ARSceneView.
  47.     // Check https://github.com/SceneView/sceneview-android/issues/450 for more details.
  48.     val cameraNode = remember { ARSceneView.createARCameraNode(engine) }
  49.  
  50.     // Currently loaded model
  51.     var modelNode by remember { mutableStateOf<ModelNode?>(null) }
  52.  
  53.     // Whether to show detected plane or not
  54.     var planeRenderer by remember { mutableStateOf(true) }
  55.  
  56.     // Reason for plane detection failure
  57.     var trackingFailureReason by remember {
  58.         mutableStateOf<TrackingFailureReason?>(null)
  59.     }
  60.     var frame by remember { mutableStateOf<Frame?>(null) }
  61.  
  62.     var animateModel by remember { mutableStateOf(false) }
  63.     var showCoachingOverlay by remember { mutableStateOf(true) }
  64.     var showGestureOverlay by remember { mutableStateOf(false) }
  65.  
  66.     LaunchedEffect(key1 = modelName) {
  67.         val path = "models/sofa.glb"
  68.  
  69.         modelNode = modelLoader.createModelInstance(path).let { instance ->
  70.             ModelNode(
  71.                 modelInstance = instance,
  72.             ).apply {
  73.                 // Change models initial scale. Currently using width
  74.                 scale = Scale(0.3F / size.x)
  75.                 // Reset model's initial rotation
  76.                 rotation = Rotation()
  77.                 // Enable custom gestures on model
  78.                 enableGestures()
  79.             }
  80.         }
  81.     }
  82.  
  83.     Box(modifier = Modifier.fillMaxSize()) {
  84.         ARScene(
  85.             modifier = Modifier.fillMaxSize(),
  86.             cameraNode = cameraNode,
  87.             childNodes = childNodes,
  88.             engine = engine,
  89.             view = view,
  90.             modelLoader = modelLoader,
  91.             onGestureListener = rememberOnGestureListener(
  92.                 onSingleTapConfirmed = { _, _ ->
  93.                     if (animateModel) {
  94.                         animateModel = false
  95.                         showGestureOverlay = true
  96.                     }
  97.                 },
  98.                 onMove = { _, event, node ->
  99.                     if (node == null) return@rememberOnGestureListener
  100.                     // Move gesture to move model node. Instead of changing model position
  101.                     // change anchor node position (parent of model node)
  102.                     frame?.hitTest(event)?.firstOrNull()?.hitPose?.position?.let { position ->
  103.                         val anchor = (node.parent as? AnchorNode) ?: node
  104.                         anchor.worldPosition = position
  105.                     }
  106.                 }
  107.             ),
  108.             sessionCameraConfig = { session -> session.disableDepthConfig() },
  109.             sessionConfiguration = { _, config -> config.configure() },
  110.             planeRenderer = planeRenderer,
  111.             onTrackingFailureChanged = {
  112.                 showCoachingOverlay = it != null
  113.                 trackingFailureReason = it
  114.             },
  115.             onSessionUpdated = { session, updatedFrame ->
  116.                 frame = updatedFrame
  117.  
  118.                 // If no model is placed then create anchor node and add
  119.                 // model node to that anchor
  120.                 if (childNodes.isEmpty()) {
  121.                     updatedFrame.createCenterAnchorNode(engine)?.let { anchorNode ->
  122.                         modelNode?.let {
  123.                             animateModel = true
  124.                             anchorNode.addChildNode(it)
  125.                             childNodes.add(anchorNode)
  126.                             planeRenderer = false
  127.                             showCoachingOverlay = false
  128.                         }
  129.                     }
  130.                 } else if (animateModel) {
  131.                     (childNodes.firstOrNull() as? AnchorNode)?.apply {
  132.                         val (x, y) = view.viewport.run {
  133.                             width / 2F to height / 2F
  134.                         }
  135.  
  136.                         val newPosition = updatedFrame.getPose(x, y)?.position ?: return@ARScene
  137.                         worldPosition = Position(newPosition.x, newPosition.y, newPosition.z)
  138.                     }
  139.                 }
  140.             },
  141.         )
  142.     }
  143. }
  144.  
  145. /**
  146.  * Disable Depth API
  147.  * Depth API may give better results. But, some devices (tested on Samsung S20+)
  148.  * creates too much lag. Reason is unknown. Looks like the image acquired
  149.  * with `frame.acquireCameraImage()` is not realised. so we won't be able to acquire
  150.  * new frames when the rate limit is exceeded.
  151.  */
  152. private fun Session.disableDepthConfig(): CameraConfig {
  153.     val filter = CameraConfigFilter(this)
  154.     filter.setDepthSensorUsage(EnumSet.of(CameraConfig.DepthSensorUsage.DO_NOT_USE))
  155.     val cameraConfigList = getSupportedCameraConfigs(filter)
  156.     return cameraConfigList.first()
  157. }
  158.  
  159. /**
  160.  * Configurations for ARSession
  161.  */
  162. private fun Config.configure() {
  163.     depthMode = Config.DepthMode.DISABLED
  164.     updateMode = Config.UpdateMode.LATEST_CAMERA_IMAGE
  165.     instantPlacementMode = Config.InstantPlacementMode.LOCAL_Y_UP
  166.     planeFindingMode = PlaneFindingMode.HORIZONTAL
  167.     lightEstimationMode = Config.LightEstimationMode.ENVIRONMENTAL_HDR
  168. }
  169.  
  170. /**
  171.  * Create anchor node at the center of the screen
  172.  */
  173. private fun Frame.createCenterAnchorNode(engine: Engine): AnchorNode? =
  174.     getUpdatedPlanes().firstOrNull()
  175.         ?.let { it.createAnchorOrNull(it.centerPose) }
  176.         ?.let { anchor ->
  177.             AnchorNode(engine, anchor).apply {
  178.                 isEditable = false
  179.                 isPositionEditable = false
  180.                 updateAnchorPose = false
  181.             }
  182.         }
  183.  
  184. /**
  185.  * Get real world position from the given [x] & [y] coordinates.
  186.  */
  187. private fun Frame.getPose(x: Float, y: Float): Pose? =
  188.     hitTest(x, y).firstOrNull()?.hitPose
  189.  
  190. /**
  191.  * Enable gestures for receiver [ModelNode].
  192.  *
  193.  * The default gestures for scale has exponential effect.
  194.  * So using custom gesture for scaling is better.
  195.  */
  196. private fun ModelNode.enableGestures() {
  197.     var previousScale = 0F
  198.     onScale = { _, _, value ->
  199.         scale = Scale(previousScale * value)
  200.         false
  201.     }
  202.     onScaleBegin = { _, _ ->
  203.         previousScale = scale.x
  204.         false
  205.     }
  206.     onScaleEnd = { _, _ ->
  207.         previousScale = 0F
  208.     }
  209.  
  210.     // Set rendering priority higher to properly load occlusion.
  211.     setPriority(7)
  212.     // Model Node needs to be editable for independent rotation from the anchor rotation
  213.     isEditable = true
  214.     isScaleEditable = true
  215.     isRotationEditable = true
  216.     editableScaleRange = Float.MIN_VALUE..Float.MAX_VALUE
  217. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement