How to position a SCNNode to cover the whole SCNView? - ios

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.

Related

Position SCNNode in front of camera with just one axis of rotation

I'm trying to make an ARKit application where an SCNNode, in this case a box, is placed in front of the camera, facing the camera. As the user moves the camera around, objects are placed when a certain distance has been moved. This would leave you with a series of nodes facing the camera in a line, equally spaced.
I have this working to a certain extent, but my problem is with the rotation. I'm currently taking all axes of rotation, so as the user re-orients their phone, the rotation of the node matches. I want to restrict this to just the rotation around the y-axis. The ideal outcome is a domino-trail like look, with all the objects having the same x and z rotations, but potentially different y rotations.
I hope I've explained this clearly enough!
Here's the code I'm currently using:
func createNode(fromCameraTransform cameraTransform: matrix_float4x4) -> SCNNode {
let geometry = SCNBox(width: 0.02, height: 0.04, length: 0.01, chamferRadius: 0)
let physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(geometry: geometry))
physicsBody.mass = 1000
let node = SCNNode(geometry: geometry)
node.physicsBody = physicsBody
var translationMatrix = matrix_identity_float4x4
translationMatrix.columns.3.x = 0.05 // Moves the node down in world space
translationMatrix.columns.3.z = -0.1 // Moves the object away from the camera
node.simdTransform = simd_mul(cameraTransform, translationMatrix)
return node
}
I've tried different combinations of extracting values from the second column of the cameraTransform and setting them as eulerAngles, rotation and simdRotation, but to no avail.
I've also tried extracting values from the pointOfView of the current sceneView and assigning them to the same values as listed above, but again, no luck.
Any help would greatly appreciated!
I know a little bit about this, but am really just starting out with SceneKit and 3D transformations/matrices so be gentle with me!
I think I know what your trying to do, basically automatically drop each new domino so its evenly spaced following the camera.pointOfView trail.
You can update the new nodes euler angles y-axis to the same as the cameras pointofView eularAngles.y. So as you move the camera around the next node you are placing is always facing towards the camera (only rotating around the y-axis).
The renderer function updateAtTime below gets called everytime
the camera moves
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
// You set the camera’s pointOfView’s eularAngle for y-xis to the node you are
about to place.
node.eulerAngles.y = (sceneView.pointOfView?.eulerAngles.y)!
I had this working in a playground so it does work.
Edit: This solution above angle was having a gimbal lock problem (as you went around in a circle in would reset its angle back to the axis.
So I found this approach using SCNBillboardConstraint works without experiencing the gimbal lock problem as you go around in circle.
let yFreeConstraint = SCNBillboardConstraint()
yFreeConstraint.freeAxes = .Y
node.constraints = [yFreeConstraint]
node.eulerAngles = node.presentation.eulerAngles
node.position = position

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)

Scaling an object after rotating in SceneKit

I am trying to set up a simple scene (one spherical node and the default camera) in a square SceneView. Currently I set up the scene as below:
let scene = SCNScene()
let planet = SCNSphere(radius: 1.0)
let planetNode = SCNNode(geometry: planet)
scene.rootNode.addChildNode(planetNode)
To certain views, I also rotate the node as such:
let rotationNode = SCNNode()
rotationNode.addChildNode(planetNode)
scene.rootNode.addChildNode(rotationNode)
rotationNode.rotation = (SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: some_amount_of_radians))
What I noticed, however, is the objects that get rotated are smaller than the ones that don't get rotated. I am not really sure what the ratio is, but it seems to be dependent on how much rotation is added, to a point.
In the below screenshot, Earth is rotated 45 degrees, and the other 2 are not rotated. If I rotated it 90 degrees instead, there is no difference, which leads me to believe there is a square bounding box around the sphere and the default camera is forcing its point of view to contain this box.
I have also tried to change the euler angles, position, and scale of the rotated nodes to compensate, but no transormations I apply seem to have any effect. Any pointers for solving this camera issue would be perfect.

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.

SpriteKit: What's up with the coordinate system?

I'm teaching myself how to do SpriteKit programming by coding up a simple game that requires that I lay out a square "game field" on the left side of a landscape-oriented scene. I'm just using the stock 1024x768 view you get when creating a new SpriteKit "Game" project in XCode - nothing fancy. When I set up the game field in didMoveToView(), however, I'm finding the coordinate system to be a little weird. First of all, I expected I would have to place the board at (0, 0) for it to appear in the lower-left. Not so -- it turns out the game board has to be bumped up about 96 pixels in the y direction to work. So I end up with this weird code:
let gameFieldOrigin = CGPoint(x:0, y:96) // ???
let gameFieldSize = CGSize(width:560, height: 560)
let gameField = CGRect(origin: gameFieldOrigin, size: gameFieldSize)
gameBorder = SKShapeNode(rect: gameField)
gameBorder.strokeColor = UIColor.redColor()
gameBorder.lineWidth = 0.1
self.addChild(gameBorder) // "self" is the SKScene subclass GameScene
Furthermore, when I add a child to it (a ball that bounces inside the field), I assumed I would just use relative coordinates to place it in the center. However, I ended up having to use "absolute" coordinate and I had to offset the y-coordinate by 96 again.
Another thing I noticed is when I called touch.locationInNode(gameBorder), the coordinates were again not relative to the border, and start at (0, 96) at the bottom of the border instead of (0, 0) as I would have guessed.
So what am I missing here? Am I misunderstanding something fundamental about how coordinates work?
[PS: I wanted to add the tag "SpriteKit" to this question, but I don't have enough rep. :/]
You want to reference the whole screen as a coordinate system, but you're actually setting all the things on a scene loading from GameScene.sks. The right way to do is modify one line in your GameViewController.swift in order to set your scene size same as the screen size. Initialize scene size like this instead of unarchiving from .sks file:
let scene = GameScene(size: view.bounds.size)
Don't forget to remove the if-statement as well because we don't need it any more. In this way, the (0, 0) is at the lower-left corner.
To put something, e.g. aNode, in the center of the scene, you can set its position like:
aNode.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));

Resources