SceneKit:围绕任意点进行轨道运行而无需相机跳跃或将锚点移动到中心?

Posted

技术标签:

【中文标题】SceneKit:围绕任意点进行轨道运行而无需相机跳跃或将锚点移动到中心?【英文标题】:SceneKit: orbit around arbitrary point without camera jumping or shifting anchor point to center? 【发布时间】:2017-02-22 06:02:46 【问题描述】:

目标是围绕场景中任意但可见的点进行轨道运行。

当用户平移时,相机应该围绕这个锚点移动和旋转。

如果锚点位于屏幕中间,这将非常有效。

但是,如果锚点偏离中心 - 例如,在屏幕的左边缘 - 相机会在将锚点带入中心时短暂跳跃。

下面的代码修复了跳跃问题,但它使用户迷失方向,因为锚点缓慢地移动到中心。

相机移动与平移手势相关。

关于如何解决这个问题的任何想法?

let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didSceneViewPanOneFinger))
panRecognizer.minimumNumberOfTouches = 1
panRecognizer.maximumNumberOfTouches = 1
panRecognizer.delegate = self
sceneView.addGestureRecognizer(panRecognizer)


func didSceneViewPanOneFinger(_ sender: UIPanGestureRecognizer) 
    // Pick any visible point as long as it's not in center
    let anchorPoint = SCNVector(-15, 0, 0) 

    // Orbit camera
    cameraNode.orbitPoint(anchorPoint: anchorPoint, translation: sender.translation(in: sender.view!), state: sender.state)



class CameraNode: SCNNode 
    // Vars
    let headNode = SCNNode()
    var curXRadians = Float(0)
    var curYRadians = Float(0)
    var directLastTranslationY = Float(0)
    var reach = Float(10)
    var aimingPoint = SCNVector3()
    var lastAnchor:SCNVector3!


    init(reach: Float) 
        super.init()
        self.reach = reach

        // Call <doInit> only after all properties set
        doInit()
    


    fileprivate func doInit() 
        // Add head node
        headNode.camera = SCNCamera()
        headNode.camera!.zNear = Double(0.1)
        headNode.position = SCNVector3(x: 0, y: 0, z: 0)
        addChildNode(headNode)

        // Position camera
        position = SCNVector3(x: 0, y: minY, z: reach)
           


    func orbitPoint(anchorPoint: SCNVector3, translation: CGPoint, state: UIGestureRecognizerState) 
        // Get pan distance & convert to radians
        var xRadians = GLKMathDegreesToRadians(Float(translation.x))
        var yRadians = GLKMathDegreesToRadians(Float(translation.y))

        // Get x & y radians, adjust values to throttle rotate speed
        xRadians = (xRadians / 3) + curXRadians
        yRadians = (yRadians / 3) + curYRadians

        // Limit yRadians to prevent rotating 360 degrees vertically
        yRadians = max(Float(-M_PI_2), min(Float(M_PI_2), yRadians))

        // Save original position
        if state == .began 
            aimingPoint = lastAnchor ?? anchorPoint
         else 
            aimingPoint = SCNVector3.lerp(vectorStart: anchorPoint, vectorEnd: aimingPoint, t: 0.99)
        

        // Rotate around <anchorPoint>
        // * Compute distance to <anchorPoint>, used as radius for spherical movement
        let radius = aimingPoint.distanceTo(position)
        var newPoint = getPointOnSphere(aimingPoint, hAngle: yRadians, vAngle: xRadians, radius: radius)
        if newPoint.y < 0 
            yRadians = directLastTranslationY

            newPoint = getPointOnSphere(aimingPoint, hAngle: yRadians, vAngle: xRadians, radius: radius)
        

        // Set rotation values to avoid Gimbal Lock
        headNode.rotation = SCNVector4(x: 1, y: 0, z: 0, w: yRadians)
        rotation = SCNVector4(x: 0, y: 1, z: 0, w: xRadians)

        print("cam pos: \(position). anchor point: \(anchorPoint). radius: \(radius).")

        // Save value for next rotation?
        if state == .ended 
            curXRadians = xRadians
            curYRadians = yRadians
            lastAnchor = aimingPoint ?? anchorPoint
        
        directLastTranslationY = yRadians
    


    // Your position in 3d is given by two angles (+ radius, which in your case is constant)
    // here, s is the angle around the y-axis, and t is the height angle, measured 'down' from the y-axis.
    func getSphericalCoords(_ s: Float, t: Float, r: Float) -> SCNVector3 
        return SCNVector3(-(cos(s) * sin(t) * r),
                          sin(s) * r,
                          -(cos(s) * cos(t) * r))
    


    fileprivate func getPointOnSphere(_ centerPoint: SCNVector3, hAngle: Float, vAngle: Float, radius: Float? = nil) -> SCNVector3 
        // Update <radius>?
        var radius = radius
        if radius == nil 
            radius = reach
        

        // Compute point & return result
        let p = centerPoint - getSphericalCoords(hAngle, t: vAngle, r: radius!)
        return p
    

【问题讨论】:

你觉得这个怎么样? 【参考方案1】:

如果您想围绕一个不是相机视图中心的点旋转相机,并保持该点不在视图中心,您最好使用约束的三角形排列。

在相机视图的中心,与旋转点相邻,修复一个用作相机“查看”约束的虚拟对象。这个虚拟对象固定在旋转点上,因此相机可以随心所欲地旋转。

我希望这张图比我的话解释得更好:

【讨论】:

不,它是一个虚拟对象的约束,然后是旋转点的节点。不需要数学。如果需要数学,我做不到! @流动性 约束是 SpriteKit 和 SceneKit 的固定连接(大部分)。它们对于将事物锁定在一起并创建位置关系有点有用......@Fluidity 不过,你的大脑必须在三角中思考才能想出这样的约束想法。你的数学比你想象的要好 我不同意,@Fluidity。我不得不从假人的角度来思考:我非常、个人、非常熟悉的东西。 而“假人之路”是我即将出版的自传@Fluidity的标题

以上是关于SceneKit:围绕任意点进行轨道运行而无需相机跳跃或将锚点移动到中心?的主要内容,如果未能解决你的问题,请参考以下文章

两指合一的相机轨道旋转

SceneKit中的相机旋转

SceneKit 球体法线似乎随着相机移动而改变?

围绕轨道物体的轨道物体

SceneKit 移动相机

实现一个复杂的基于旋转的相机