SCNBillboardConstraint isn’t working. Node with constraint doesn’t change - ios

Perhaps I am not setting up the camera properly… I’m starting with a scn file with a camera. In Xcode, rotating the free camera around, the geometries rotate as expected. However, at runtime, nothing happens.
It doesn’t seem to matter if I add the constraint in code or in the editor. The look at constraint works.
It also doesn’t seem to matter if I use the camera from the scn file or if I add a camera in code.
The sample code is
class Poster: SCNNode {
let match:GKTurnBasedMatch
init(match:GKTurnBasedMatch, width:CGFloat, height:CGFloat) {
self.match = match
super.init()
// SCNPlane(width: width, height: height)
self.geometry = SCNBox(width: width, height: height, length: 0.1, chamferRadius: 0)
self.constraints = [SCNBillboardConstraint()]
self.updatePosterImage()
}
}

So… I gave up on the billboard constraint.
I’m using a SCNLookAtConstraint that looks at the camera node, with the gimbal lock enabled.
I was using a SCNPlane but it was doing weird stuff. So I went with a SCNBox for the geometry.
So, in the constructor:
self.geometry = SCNBox(width: self.size.width, height: self.size.height, length: 0.1, chamferRadius: 0)
let it = SCNLookAtConstraint(target: cameraNode)
it.isGimbalLockEnabled = true
self.constraints = [it]
It works.

You're missing ".init()" on the SCNBillboardConstraint(). This line alone did all the work for me:
node.constraints = [SCNBillboardConstraint.init()]

Try this:
A constraint that orients theNode to always point toward the current camera.
// Create the constraint
SCNBillboardConstraint *aConstraint = [SCNBillboardConstraint billboardConstraint];
theNode.constraints = #[aConstraint];
theNode is the node you want pointing to the camera. This should work.
Updated
Ok, if you were to create a sample Project with the Game template. And then make the following changes:
// create a clone of the ship, change position and rotation
SCNNode *ship2 = [ship clone];
ship2.position = SCNVector3Make(0, 4, 0);
ship2.eulerAngles = SCNVector3Make(0, M_PI_2, 0);
[scene.rootNode addChildNode:ship2];
// Add the constraint to `ship`
SCNBillboardConstraint *aConstraint = [SCNBillboardConstraint billboardConstraint];
ship.constraints = #[aConstraint];
ship is constrained but ship2 isn't.
If you were to add this:
ship2.constraints = #[aConstraint];
Now, both will face the camera. Isn't this what you are looking for?

By any chance are you using the allowsCameraControl property on SCNView?
If so, remember setting that will add a new camera to your scene (cloning an existing camera if one is present to match its settings), so if you create a constraint to your own camera, the constraint will not be linked to the camera that’s actually being used.
Per Apple in their WWDC 2017 video, they say that property is really for debugging purposes, not for a real-world use.
Simply put, you have to ensure you are moving around your own camera, not relying on the auto-created one.

Related

Limit Scene Kit Camera Rotation around a single (y) axis in iOS

So I have a sphere in scene kit and currently, I am orbiting the sphere with the default .allowsCameraControl = true. I want to limit the orbit of the camera to only orbit the Y-axis (basically it will look like a globe spinning).
I have tried messing around with spinning the actual sphere with pan gestures, but that seemed to complicate things rather quickly and I would like to keep the default physics and gestures involved with .allowsCameraControl = true if at all possible.
Below is what I have currently. Thanks!
private func createSceneHelpers() {
scnView.allowsCameraControl = true
}
private func createCamera() {
cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 5)
myScene.rootNode.addChildNode(cameraNode)
}
You can take a look at the SCNCameraController class and its maximumVerticalAngle property. The SCNInteractionModeOrbitTurntable interaction mode is also of interest. Note that if the online documentation is lacking, the documentation in SceneKit's header file as more often up-to-date.

How to use SKConstraint in swift

I have a moving camera in my scene, which always follows my player. But I also have some other content(On screen controls) which I want to stay in a single place on the screen, but when the camera moves, The controls are moving away to. How would I go about doing this. I have searched a lot, and found the SKConstraint, but I couldn't find any tutorials to use it in swift 3.
Should I be using the SKConstraint? If yes, how can I use it, if no, how do I keep the controls at a certain position on the screen at all times.
I also know that I could change the position of the controls in the update method, but I do not want to do that as there are many on screen controls, and I try to refrain from writing code in the update method as much as possible.
Any help would be appreciated, thanks!
You can use an SKConstraint to cause a camera to follow a character.
First, create a camera node and a hero and add them to the scene
let cameraNode = SKCameraNode()
let hero = SKSpriteNode(imageNamed: "Spaceship")
addChild(hero)
camera = cameraNode
addChild(cameraNode)
Next, create a constraint and assign it to the camera node's constraints property
let range = SKRange(constantValue:0)
let constraint = SKConstraint.distance(range, to: hero)
cameraNode.constraints = [constraint]
Lastly, if you have controls or labels that need to be at fixed locations relative to the camera, you can add them to the camera node
let label = SKLabelNode(text: "Score: 123")
// Position the label relative to the camera node
label.position = CGPoint(x: 100, y: 100)
cameraNode.addChild(label)

How to position a SCNNode to cover the whole SCNView?

I am very new to SceneKit and your help will be really appreciated!
I have a 200x200 sized SCNView in my UIView, which is at the centre of super view.
I want to put a SCNCylinder inside, such that the SCNCylinder covers full SCNView. I read that all these views of Scenekit are defined in meters, so how do I form a relationship between the dimensions of my screen and the
SCNCylinder.
I tried:
var coinNode = SCNNode()
let coinGeometry = SCNCylinder(radius: 100, height: 2)
coinNode = SCNNode(geometry: coinGeometry)
coinNode.position = SCNVector3Make(0, 0, 0)
coinScene.rootNode.addChildNode(coinNode)
let rotate90AboutZ = SCNAction.rotateByX(-CGFloat(M_PI_2), y: 0.0, z: CGFloat(M_PI_2), duration: 0.0)
coinNode.runAction(rotate90AboutZ)
ibOutletScene.scene = coinScene
But this leaves a margin between my coinScene and the ibOutletScene. How do I remove this space?
I also tried adding Camera:
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3Make(0, 0, 100)
coinScene.rootNode.addChildNode(cameraNode)
But I see random behaviour with this and the coinNode gets hidden! How should I position my camera? Or is there any other way to remove extra space from my ibOutletScene?
Edit:
This is how it looks if I don't add camera. There is a margin between red scene and green coin. I tried multiple sizes for the coin, but I am unable to remove this margin unless I add a camera. But, If I add camera, I get another problem, mentioned below this screenshot.
If I don't add the camera, The rotation animation on coin works perfectly, but If I add camera,the rotation enlarges the and then becomes small again with the animation. How can I rotate it on its axis, without increasing the size?
I am using following code to rotate the coin:
The same code works fine without camera, but enlarges the coin after adding camera. Checkout the snapshot.
let rotate = SCNAction.rotateByX(0, y: -2 * CGFloat(M_PI_2), z: 0, duration: 2)
coinNode.runAction(rotate)
The random behavior might be caused by the last line in your first code snippet. You're starting an animation, and then adding the scene to the view.
Instead, build your scene, attach it to the view, and then start your animation. Setting a non-zero duration for the action will give you a more pleasing transition.
As for the extra space, it would help us understand if you post a screenshot. But you're going to have to do a bit of trigonometry.
It looks like you have a scene that you want to be blocked by a coin, that then rotates out of the way? Simulate that yourself with real objects. Put your eye down at the edge of your desk. Put a coin out a ways from your eye. How far does that coin have to be in order to block particular objects farther away on your desk?
In SceneKit, you can query the field of view of the SCNCamera. You know the size of your coin and the size of the view. Calculate the distance from the camera needed for the projected diameter of your coin to equal the width of your view. Put the coin there.

Moving SCNLight with SCNAction

I have a spotlight, created with the code beneath, casting shadows on all of my nodes:
spotLight.type = SCNLightTypeSpot
spotLight.spotInnerAngle = 50.0
spotLight.spotOuterAngle = 150.0
spotLight.castsShadow = true
spotLight.shadowMode = SCNShadowMode.Deferred
spotlightNode.light = spotLight
spotlightNode.eulerAngles = SCNVector3(x: GLKMathDegreesToRadians(-90), y: 0, z: 0)
spotlightNode.position = levelData.coordinatesForGridPosition(column: 0, row: playerGridRow)
spotlightNode.position.y = 1.5
rootNode.addChildNode(spotlightNode)
The scene is moving along the z axis, and the camera has an infinite animation that makes it move:
let moveAction = SCNAction.moveByX(0.0, y: 0.0, z: CGFloat(-GameVariables.segmentSize / 2), duration: 2.0)
cameraContainerNode.runAction(SCNAction.repeatActionForever(moveAction))
As the camera moves though, the light doesn't, so after a while, the whole scene is dark. I want to move the light with the camera, however if I apply to the light node the same moving animation, all the shadows start to flicker. I tried to change the SCNShadowMode to Forward and the light type to Directional, but the flickering is still there. With directional, I actually loose most of my shadows. If I create a new light node later on, it will seem that I have two "suns", which of course is impossible. The final aim is simply to have an infinite light that shines parallel to the scene from the left, casting all the shadows to the right. Any ideas?
Build a node tree to hold both spotlight and camera.
Create, say, cameraRigNode as an SCNNode with no geometry. Create cameraContainerNode and spotlightNode the same way you are now. But make them children of cameraRigNode, not the scene's root node.
Apply moveAction to cameraRigNode. Both the camera and the light will now move together.

SCNText with SCNLookAtConstraint looking the wrong way

I want to display text alongside my 3D model using SceneKit in iOS. It just has to appear like it is 2D. So the text should always be facing the camera, however I rotate the 3D model. I have an SCNNode with a SCNText geometry and I attached an SCNLookAtConstraint like so:
let constraint = SCNLookAtConstraint(target: cameraNode)
constraint.gimbalLockEnabled = true
textNode.constraints = [constraint]
They seem to be facing the camera, but only the wrong way! I see all text mirrored! Also, sometimes the text rotates, I want it to be aligned horizontally at all times. So this is all the behaviour I don't want.
Can somebody help me out here? I just want to have some text that follows a few nodes in my 3D object. I don't care how. It doesn't have to be SCNText for all I care, if this can be done with a simple UILabel: fine with me! Just tell me how!
From the documentation:
When SceneKit evaluates a look-at constraint, it updates the constrained node’s transform property so that the node’s negative z-axis points toward the constraint’s target node.
You can workaround that by setting the node's pivot or by using an intermediate node. That node would be constrained, and would have the text as a child node (which would be rotated by π along the y axis)
Update
Starting iOS 11.0 the SCNLookAtConstraint class exposes the localFront property which allows you to specify a different front axis. It defaults to (0, 0, -1) and you can change it to (0, 0, 1) to achieve what you want.
Based on #mnuages response, setting the node's pivot to the following should do the trick.
node.pivot = SCNMatrix4Rotate(node.pivot, Float.pi, 0, 1, 0)
I faced this problem on iOS11 beta 5, Swift4:
//1. Rotate your `textNode`, and don't add `textNode` into `sceneView`
textNode.eulerAngles = SCNVector3Make(0, .pi, 0)
//2. Create new wrapper node, add `textNode` as a child node
let textWrapperNode = SCNNode()
textWrapperNode.addChildNode(textNode)
//3. Add constraint for wrapper node
let constraint = SCNLookAtConstraint(target: cameraNode)
constraint.gimbalLockEnabled = true
textWrapperNode.constraints = [constraint]
//4. Add wrapper node into `sceneView`
sceneView.scene.rootNode.addChildNode(textNode)

Resources