简体   繁体   English

SceneKit:围绕任意点运行而没有相机跳跃或将锚点移动到中心?

[英]SceneKit: orbit around arbitrary point without camera jumping or shifting anchor point to center?

The goal is to orbit around an arbitrary, but visible, point in a scene.目标是围绕场景中任意但可见的点运行。

As the user pans, the camera should move and rotate around this anchor point.当用户平移时,相机应该围绕这个锚点移动和旋转。

This works wonderfully if the anchor point is in the middle of the screen.如果锚点位于屏幕中间,这将非常有效。

However, if the anchor point is off-center -- say, at the left edge of the screen -- the camera jumps momentarily as it brings the anchor point into the center.然而,如果锚点偏离中心——比如说,在屏幕的左边缘——摄像机会在将锚点带入中心时瞬间跳跃。

The code below fixed the jumping issue, but it disorients users in that the anchor point slowly shifts to the center.下面的代码修复了跳跃问题,但它使用户迷失方向,因为锚点缓慢地移动到中心。

The camera movement is connected to the pan gesture.相机移动与平移手势有关。

Any ideas on how to solve this problem?关于如何解决这个问题的任何想法?

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

If you want to rotate the camera around a point that's not the centre of the camera's view, and keep that point not at the centre of the view, you're best off using a triangle arrangement of constraints.如果您想围绕一个不是相机视图中心的点旋转相机,并保持该点不在视图的中心,那么最好使用约束的三角形排列。

At the centre of the camera's view, adjacent to the rotation point, fix a dummy object that's used as a camera "look at" constraint.在相机视图的中心,靠近旋转点,固定一个虚拟对象,用作相机“看”约束。 This dummy object is affixed to the rotation point, so the camera rotates as you desire.这个虚拟对象固定在旋转点上,因此相机可以根据需要旋转。

I hope this diagram explains better than my words:我希望这张图比我的话解释得更好:

在此处输入图片说明

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM