You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

非AR场景SceneView自定义旋转后3D模型偏移无法居中问题

问题描述

通过SceneView实现3D模型加载视图时,添加自定义模型旋转控制类后,模型出现向上的位置偏移,无法在屏幕居中显示,偏移效果如下:
偏移效果示意图
需要在保留现有旋转交互功能的前提下,修复模型居中显示问题。
原实现代码如下:

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
    }

    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 *
                cos(Math.toRadians(lat)) *
                sin(Math.toRadians(long))
                ).toFloat()
    }

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

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

    override fun onActivated(node: Node?) {
        super.onActivated(node)
        Handler(Looper.getMainLooper()).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

        transformCamera(lat, long)
    }

    private fun transformCamera(latitude: Double, longitude: Double) {
        transformableNode.scene?.camera?.apply {
            localRotation = getRotation(longitude, latitude)
            localPosition = getPosition(latitude, longitude)
        }
    }

    private fun getPosition(latitude: Double, longitude: Double) = Vector3(
        getX(latitude, longitude),
        getY(latitude),
        getZ(latitude, longitude)
    )

    private fun getRotation(longitude: Double, latitude: Double): Quaternion? {
        var rotation = Quaternion.eulerAngles(Vector3(0F, 0F, 0F))
        rotation = Quaternion.multiply(
            rotation, Quaternion(
                Vector3(0f, 1f, 0f),
                (longitude).toFloat()
            )
        )
        rotation = Quaternion.multiply(
            rotation, Quaternion(
                Vector3.right(),
                (-latitude).toFloat()
            )
        )
        return rotation
    }

    public override fun onEndTransformation(gesture: DragGesture) {
        //no-op
    }
}
问题原因

偏移核心来自两点:

  • 初始经纬度参数设置不合理,初始纬度26度对应的相机Y轴位置本身偏高,视线没有对准模型中心原点
  • 手动计算相机四元数旋转存在逻辑误差,没有做对准模型中心的校正,直接导致视角上模型整体上移
修复方案

调整初始参数,替换手动旋转计算逻辑为SceneView内置的中心对准方法,完全保留原有拖拽旋转交互,修改后代码如下:

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

    companion object {
        // 重置初始经纬度,初始状态下相机正对模型中心
        private const val initialLat = 0.0
        private const val initialLong = 0.0

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

    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 *
                cos(Math.toRadians(lat)) *
                sin(Math.toRadians(long))
                ).toFloat()
    }

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

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

    override fun onActivated(node: Node?) {
        super.onActivated(node)
        Handler(Looper.getMainLooper()).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()

        // 增加纬度边界限制,避免旋转到模型上下方出现视角翻转
        lat = (lat + deltaAngleY).coerceIn(-89.0, 89.0)
        long -= deltaAngleX

        transformCamera(lat, long)
    }

    private fun transformCamera(latitude: Double, longitude: Double) {
        transformableNode.scene?.camera?.apply {
            localPosition = getPosition(latitude, longitude)
            // 直接对准模型中心位置,保证模型始终处于视野正中
            lookAt(transformableNode.worldPosition)
        }
    }

    private fun getPosition(latitude: Double, longitude: Double) = Vector3(
        getX(latitude, longitude),
        getY(latitude),
        getZ(latitude, longitude)
    )

    public override fun onEndTransformation(gesture: DragGesture) {
        //no-op
    }
}
修改说明
  • 初始经纬度重置为0,初始状态下相机在Z轴正方向正对模型中心,消除初始偏移
  • 删除误差较大的手动四元数旋转计算逻辑,使用内置lookAt()方法直接对准模型节点中心,从根源避免视角偏移
  • 增加纬度范围限制,避免拖拽旋转到模型正上方/正下方时出现视角翻转问题
  • 原有拖拽旋转的交互逻辑、旋转速率参数完全保留,不影响原有使用体验

内容的提问来源于stack exchange,提问作者Ludiras

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 19:15:39