如何在 Android SceneForm 中使用拖动手势旋转 3D 模型

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

我遵循了解决方案 here for rotating a TransformableNode on the X axis based on the user's DragGesture, using the Sceneform Android SDK。但是,我也想在 Y 轴和 Z 轴上旋转,类似于 ARCore SceneViewer 的做法。

我怎样才能做到这一点?

我目前拥有的在左侧(仅在 X 轴上旋转),而所需的在右侧(在所有轴上旋转,如在 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) {}
}

我在这里找到了可行的解决方案:https://github.com/chnouman/SceneView

这里是相关的代码片段,经过我的一些改编使其适用于 .glb 个文件。

我已经分叉了这个 repo,如果有人对此感兴趣,我正在致力于键盘输入支持。

渲染对象:

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)
            }

将对象添加到 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
                    )
                }
        }
    }

自定义 TransformableNode:

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

自定义 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) {}


}