VIN
VIN

Reputation: 6947

How to rotate an 3D model using DragGesture in Android SceneForm

I followed the solution here for rotating a TransformableNode on the X axis based on the user's DragGesture, using the Sceneform Android SDK. However, I would also like to rotate on the Y and Z axis as well, similar to how ARCore SceneViewer does it.

How can I achieve that?

What I have currently is on the left (rotates only on X axis), and what is desired is on the right (rotates on all axes, as in ARCore Scene Viewer).

class DragRotationController(transformableNode: BaseTransformableNode, gestureRecognizer: DragGestureRecognizer) :
    BaseTransformationController<DragGesture>(transformableNode, gestureRecognizer) {

    // Rate that the node rotates in degrees per degree of twisting.
    var rotationRateDegrees = 0.5f

    public override fun canStartTransformation(gesture: DragGesture): Boolean {
        return transformableNode.isSelected
    }

    public override fun onContinueTransformation(gesture: DragGesture) {

        var localRotation = transformableNode.localRotation

        val rotationAmountX = gesture.delta.x * rotationRateDegrees
        val rotationDeltaX = Quaternion(Vector3.up(), rotationAmountX)
        localRotation = Quaternion.multiply(localRotation, rotationDeltaX)

        // *** this only rotates on X axis. How do I rotate on all axes? ***

        transformableNode.localRotation = localRotation
    }

    public override fun onEndTransformation(gesture: DragGesture) {}
}

Upvotes: 0

Views: 1056

Answers (1)

VIN
VIN

Reputation: 6947

I was able to find a working solution here: https://github.com/chnouman/SceneView

Here are the relevant snippets of code, with some of my adaptations to make it work for .glb files.

I have forked this repo and am working on keyboard input support if anyone's interested in that.

Rendering the object:

private fun renderLocalObject() {

        skuProgressBar.setVisibility(View.VISIBLE)
        ModelRenderable.builder()
            .setSource(this,
                RenderableSource.builder().setSource(
                    this,
                    Uri.parse(localModel),
                    RenderableSource.SourceType.GLB)/*RenderableSource.SourceType.GLTF2)*/
                    .setScale(0.25f)
                    .setRecenterMode(RenderableSource.RecenterMode.ROOT)
                    .build())
            .setRegistryId(localModel)
            .build()
            .thenAccept { modelRenderable: ModelRenderable ->
                skuProgressBar.setVisibility(View.GONE)
                addNodeToScene(modelRenderable)
            }

Adding the object to the SceneView:

private fun addNodeToScene(model: ModelRenderable) {
        if (sceneView != null) {
            val transformationSystem = makeTransformationSystem()
            var dragTransformableNode = DragTransformableNode(1f, transformationSystem)
            dragTransformableNode?.renderable = model
            sceneView.getScene().addChild(dragTransformableNode)
            dragTransformableNode?.select()
            sceneView.getScene()
                .addOnPeekTouchListener { hitTestResult: HitTestResult?, motionEvent: MotionEvent? ->
                    transformationSystem.onTouch(
                        hitTestResult,
                        motionEvent
                    )
                }
        }
    }

Custom TransformableNode:

class DragTransformableNode(val radius: Float, transformationSystem: TransformationSystem) :
    TransformableNode(transformationSystem) {
    val dragRotationController = DragRotationController(
        this,
        transformationSystem.dragRecognizer
    )
}

Custom TransformationController:

class DragRotationController(
        private val transformableNode: DragTransformableNode,
        gestureRecognizer: DragGestureRecognizer
) :
    BaseTransformationController<DragGesture>(transformableNode, gestureRecognizer) {

    companion object {

        private const val initialLat = 26.15444376319647
        private const val initialLong = 18.995950736105442

        var lat: Double = initialLat
        var long: Double = initialLong
    }

    // Rate that the node rotates in degrees per degree of twisting.
    private var rotationRateDegrees = 0.5f

    public override fun canStartTransformation(gesture: DragGesture): Boolean {
        return transformableNode.isSelected
    }

    private fun getX(lat: Double, long: Double): Float {
        return (transformableNode.radius * Math.cos(Math.toRadians(lat)) * Math.sin(Math.toRadians(long))).toFloat()
    }

    private fun getY(lat: Double, long: Double): Float {
        return transformableNode.radius * Math.sin(Math.toRadians(lat)).toFloat()
    }

    private fun getZ(lat: Double, long: Double): Float {
        return (transformableNode.radius * Math.cos(Math.toRadians(lat)) * Math.cos(Math.toRadians(long))).toFloat()
    }

    override fun onActivated(node: Node?) {
        super.onActivated(node)
        Handler().postDelayed({
            transformCamera(lat, long)
        }, 0)
    }

    public override fun onContinueTransformation(gesture: DragGesture) {

        val rotationAmountY = gesture.delta.y * rotationRateDegrees
        val rotationAmountX = gesture.delta.x * rotationRateDegrees
        val deltaAngleY = rotationAmountY.toDouble()
        val deltaAngleX = rotationAmountX.toDouble()

        long -= deltaAngleX
        lat += deltaAngleY

        //lat = Math.max(Math.min(lat, 90.0), 0.0)

        transformCamera(lat, long)
    }

    private fun transformCamera(lat: Double, long: Double) {
        val camera = transformableNode.scene?.camera

        var rot = Quaternion.eulerAngles(Vector3(0F, 0F, 0F))
        val pos = Vector3(getX(lat, long), getY(lat, long), getZ(lat, long))
        rot = Quaternion.multiply(rot, Quaternion(Vector3.up(), (long).toFloat()))
        rot = Quaternion.multiply(rot, Quaternion(Vector3.right(), (-lat).toFloat()))
        camera?.localRotation = rot
        camera?.localPosition = pos
    }

    fun resetInitialState() {
        transformCamera(initialLat, initialLong)
    }



    public override fun onEndTransformation(gesture: DragGesture) {}


}

Upvotes: 0

Related Questions