Scncamera Limit Arcball Rotation

SCNCamera limit arcball rotation

It looks like you're almost there, using just the @Rickster code from
the answer you cited.

The change you could make would be in these lines:

self.cameraOrbit.eulerAngles.y = Float(-2 * M_PI) * widthRatio
self.cameraOrbit.eulerAngles.x = Float(-M_PI) * heightRatio

which implicitly allow pitch and yaw to cover the entire
sphere. That's where you can do your limiting. For instance,
instead of allowing the pitch (eulerAngles.x) to vary from 0
to -π, you could do

self.cameraOrbit.eulerAngles.x = Float(-M_PI_2) + Float(-M_PI_2) * heightRatio

to vary smoothly between -π/2 and -π, using full screen
vertical scrolling to cover that range. Or you could put
hard min/max limits/checks in those two lines to constrain
to a particular area of the globe.

(Edit to address the inertia comment)

For rotational damping, or inertia, I'd approach it by using the built in SceneKit Physics, and perhaps put the camera on an invisible (no geometry) SCNNode. That camera node becomes a gimbal, similar to the approach taken in this project: An interactive seven-foot globe created entirely in RubyMotion and SceneKit.

The virtual gimbal then gets an SCNPhysicsBody (you add that, it doesn't come with one by default) with some damping. Or perhaps you put the physics on your central object, and give that object some angularDamping.

SCNNode (SCNCamera) rotation

if you want to set the rotation directly, expressed in roll, pitch, and yaw:
Try

node.eulerAngles

For example in SCNVector3Make(x,y,z) x, y and z are described in radians.
Which means that one whole time around is pi (3.141592653589793238462632795 or M_PI) x 2.0.

So you may want to try rotating your node to SCNVector3Make(0, M_PI / 2, M_PI / 4)

Rotate SCNCamera node looking at an object around an imaginary sphere

It might help to break down your issue into subproblems.

Setting the Scene

First, think about how to organize your scene to enable the kind of motion you want. You talk about moving the camera as if it's attached to an invisible sphere. Use that idea! Instead of trying to work out the math to set your cameraNode.position to some point on an imaginary sphere, just think about what you would do to move the camera if it were attached to a sphere. That is, just rotate the sphere.

If you wanted to rotate a sphere separately from the rest of your scene contents, you'd attach it to a separate node. Of course, you don't actually need to insert a sphere geometry into your scene. Just make a node whose position is concentric with the object you want your camera to orbit around, then attach the camera to a child node of that node. Then you can rotate that node to move the camera. Here's a quick demo of that, absent the scroll-event handling business:

let camera = SCNCamera()
camera.usesOrthographicProjection = true
camera.orthographicScale = 9
camera.zNear = 0
camera.zFar = 100
let cameraNode = SCNNode()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 50)
cameraNode.camera = camera
let cameraOrbit = SCNNode()
cameraOrbit.addChildNode(cameraNode)
cubeScene.rootNode.addChildNode(cameraOrbit)

// rotate it (I've left out some animation code here to show just the rotation)
cameraOrbit.eulerAngles.x -= CGFloat(M_PI_4)
cameraOrbit.eulerAngles.y -= CGFloat(M_PI_4*3)

Here's what you see on the left, and a visualization of how it works on the right. The checkered sphere is cameraOrbit, and the green cone is cameraNode.

camera rotate around cubecamera rotate visualization

There's a couple of bonuses to this approach:

  • You don't have to set the initial camera position in Cartesian coordinates. Just place it at whatever distance you want along the z-axis. Since cameraNode is a child node of cameraOrbit, its own position stays constant -- the camera moves due to the rotation of cameraOrbit.
  • As long as you just want the camera pointed at the center of this imaginary sphere, you don't need a look-at constraint. The camera points in the -Z direction of the space it's in -- if you move it in the +Z direction, then rotate the parent node, the camera will always point at the center of the parent node (i.e. the center of rotation).

Handling Input

Now that you've got your scene architected for camera rotation, turning input events into rotation is pretty easy. Just how easy depends on what kind of control you're after:

  • Looking for arcball rotation? (It's great for direct manipulation, since you can feel like you're physically pushing a point on the 3D object.) There are some questions and answers about that already on SO -- most of them use GLKQuaternion. (UPDATE: GLK types are "sorta" available in Swift 1.2 / Xcode 6.3. Prior to those versions you can do your math in ObjC via a bridging header.)
  • For a simpler alternative, you can just map the x and y axes of your gesture to the yaw and pitch angles of your node. It's not as spiffy as arcball rotation, but it's pretty easy to implement -- all you need to do is work out a points-to-radians conversion that covers the amount of rotation you're after.

Either way, you can skip some of the gesture recognizer boilerplate and gain some handy interactive behaviors by using UIScrollView instead. (Not that there isn't usefulness to sticking with gesture recognizers -- this is just an easily implemented alternative.)

Drop one on top of your SCNView (without putting another view inside it to be scrolled) and set its contentSize to a multiple of its frame size... then during scrolling you can map the contentOffset to your eulerAngles:

func scrollViewDidScroll(scrollView: UIScrollView) {
let scrollWidthRatio = Float(scrollView.contentOffset.x / scrollView.frame.size.width)
let scrollHeightRatio = Float(scrollView.contentOffset.y / scrollView.frame.size.height)
cameraOrbit.eulerAngles.y = Float(-2 * M_PI) * scrollWidthRatio
cameraOrbit.eulerAngles.x = Float(-M_PI) * scrollHeightRatio
}

On the one hand, you have to do a bit more work for infinite scrolling if you want to spin endlessly in one or both directions. On the other, you get nice scroll-style inertia and bounce behaviors.

SceneKit - Adding drift to a sphere rotated via a pan gesture

The way I've handled things like this before is to split the gesture handler into sections for the gesture state beginning, changing and ending. If the rotation is working for you all you need to do is add code to the bottom of your if (sender.state == .Ended) statement.

After lastHeightRatio = heightRatio you can find the velocity of the pan in the view using sender.velocityInView(sender.view!) and then find the horizontal component as before, then add an SCNAction to rotate the node with an EaseOut timing mode.

You'll likely need a multiplier to translate the velocity in the view (measured in points) to the angle you wish to rotate through (measured in radians). A velocity of 10 points, which is relatively small, would result in a very fast rotation.

Also you'll need to remove all actions from the node when the gesture begins again if you want a touch to stop the residual rotation.

Some sampel code would be:

if (sender.state == .Ended) {
lastWidthRatio = widthRatio
lastHeightRatio = heightRatio

// Find velocity
let velocity = sender.velocityInView(sender.view!)

// Create Action for whichever axis is the correct one to rotate about.
// The 0.00001 is just a factor to provide the rotation speed to pan
// speed ratio you'd like. Play with this number and the duration to get the result you want
let rotate = SCNAction.rotateByX(velocity.x * 0.00001, y: 0, z: 0, duration: 2.0)

// Run the action on the camera orbit node (if I understand your setup correctly)
cameraOrbit.runAction(rotate)
}

This should be enough to get you started.

Set SCNNodes to have constant y-axis value?

It took me a few days, but I finally figured out the fix.

I implemented the camera node as rickster suggested and set allowsCameraControl to false. I then implemented a UIPanGestureRecognizer and tied it to a function, as suggested here, which was also based on rickster's answer.

It now does what I want it to. Thanks a lot, everybody!

Have SCNCamera follow a node at a fixed distance?

You are only using SCNLookAtConstraint, which as its name says, make the camera Look At the object only. (You only need to rotate your head to look at something)

To make the Camera move with it, you will need either a SCNTransformConstraint (documentation here), or simply make the Camera Node a child of the object you want it to follow.

In case you want the Camera to smoothly follow the object, and be constrained only by a distance (as if it was dragged by a rope), the SCNTransformConstraint is the way to go.



Related Topics



Leave a reply



Submit