The Simpler Solution Prevails
My current project uses OS X’s SceneKit for some custom 3D stuff. The application has uses a SCNView for view the 3D content that the user will manipulate. The view is configured to allow the user to control the camera in the 3D environment. The default control allows the user to move the camera anywhere, and this includes moving the camera beneath the floor of the 3D environment (or negative y-values). That is something I did not want, so I sought out to “fix” things so that I could have the desired restriction of no movement below y = 0.
The plan involved subclassing SCNView and overriding methods from parent classes NSView and NSResponder pertaining to magnification and touches on the trackpad. While the magnification tweak worked, my attempts with touchesBegan: and its sisters did not produce consistent results. This pushed me to do more learning about SceneKit which led me to SCNConstraint.
After reading up on SCNConstraint I started thinking about applying this to a node containing a camera. Using StackOverflow, I found example code applying a constraint to a node for restricting its position, I then came up with a new experiment: using a standard SCNView with default user camera control, apply a constraint on the y-position to the node containing the camera. The experiment yielded the desired solution. To top it off, the solution was much less messier than my first effort of subclassing SCNView.
Here is the code snippet for the method for setting up the camera:
private func setupCamera() { let camera = SCNCamera() camera.zNear = 0.1 camera.zFar = 50000.0 camera.xFov = 60.0 camera.yFov = 60.0 cameraNode = SCNNode() cameraNode.position = SCNVector3Make(0.0, 250.0, 5000.0) cameraNode.camera = camera cameraNode.constraints = [SCNConstraint]() let yMinConstraint = SCNTransformConstraint(inWorldSpace: true, with: { (node, matrix) in var newMatrix = matrix if(node.presentation.position.y < 100.0) { newMatrix.m42 = 100.0 } return newMatrix }) let zDistanceConstraint = SCNTransformConstraint(inWorldSpace: true, with: { (node, matrix) in var newMatrix = matrix if(node.presentation.position.z < 100.0) { newMatrix.m43 = 100.0 } if(node.presentation.position.z > self.planeSideLength) { newMatrix.m43 = self.planeSideLength } return newMatrix }) cameraNode.constraints!.append(yMinConstraint) cameraNode.constraints!.append(zDistanceConstraint) sceneView.scene!.rootNode.addChildNode(cameraNode) }
Here, the constant planeSideLength is primarily used for creating the plane (floor) in a different method. The node object cameraNode is an instance property. An additional constraint was added to the camera’s node for movement in the z-plane.