SceneKit: vertically center scaled SCNNode inside parent? - ios

The goal is to scale a SCNNode and vertically center it within its parent.
However, scaling a SCNNode does not affect its bounding box and computing the scaled height does not work. Without an accurate height, how can you vertically center the node inside its parent?
To illustrate the problem with using the scaled height, see the attached file, Tiki.dae. The original height of the asset (as shown by the bounding box) is 324.36. If you set the Y-scale to 0.01, however, the height doesn't become ~3.24. It becomes smaller than 3, which you can prove by fitting it comfortably within a sphere of height 3 (radius of 1.5).
The code below attempts to center a scaled node inside its parent, but it doesn't work.
Note: reference node is the fox/panda reference node from the WWDC 2015 fox demo.
// Create reference node
let referenceNode = SCNReferenceNode(URL: referenceURL)
referenceNode?.load()
// Scale reference node
let scale = Float(3)
referenceNode?.scale = SCNVector3(x: scale, y: scale, z: scale)
// Create geometry for sphere
let sphereGeometry = SCNSphere(radius: (gridSphere.geometry as! SCNSphere).radius)
//sphereGeometry.materials = gridSphere.geometry!.materials
sphereGeometry.firstMaterial!.diffuse.contents = gPurpleColor
// Create sphere to hold reference node, position at same place as <gridSphere>
let liveSphere = SCNNode(geometry: sphereGeometry)
liveSphere.position = gridSphere.position
// Center reference node inside <liveSphere>
var min = SCNVector3Zero
var max = SCNVector3Zero
referenceNode?.getBoundingBoxMin(&min, max: &max)
let referenceNodeHeight = max.y - min.y
referenceNode?.position = SCNVector3(x: 0, y: 0 - referenceNodeHeight, z: 0)
// Add reference node to <liveSphere>
liveSphere.addChildNode(referenceNode!)
// This value never changes no matter the scale value???
print(referenceNodeHeight)

Here's a Playground that adds a cube (in place of your reference node) as a child of the sphere. The cube responds to scaling (see the lines following "// Scale reference node").
//: Playground - noun: a place where people can play
import Cocoa
import SceneKit
import XCPlayground
public class GizmoNode: SCNNode {
required public override init() {
super.init()
let axisLength = CGFloat(3.0)
let offset = CGFloat(axisLength/2.0)
let axisSide = CGFloat(0.2)
let chamferRadius = CGFloat(axisSide)
let xBox = SCNBox(width: axisLength, height: axisSide, length: axisSide, chamferRadius: chamferRadius)
xBox.firstMaterial?.diffuse.contents = NSColor.redColor()
let yBox = SCNBox(width: axisSide, height: axisLength, length: axisSide, chamferRadius: chamferRadius)
yBox.firstMaterial?.diffuse.contents = NSColor.greenColor()
let zBox = SCNBox(width: axisSide, height: axisSide, length: axisLength, chamferRadius: chamferRadius)
zBox.firstMaterial?.diffuse.contents = NSColor.blueColor()
let xNode = SCNNode(geometry: xBox)
let yNode = SCNNode(geometry: yBox)
let zNode = SCNNode(geometry: zBox)
self.addChildNode(xNode)
self.addChildNode(yNode)
self.addChildNode(zNode)
print (xNode.position)
print (yNode.position)
print (zNode.position)
xNode.position.x = offset
yNode.position.y = offset
zNode.position.z = offset
print (xNode.pivot)
print (yNode.pivot)
print (zNode.pivot)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
let sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
let scene = SCNScene()
sceneView.scene = scene
sceneView.backgroundColor = NSColor.blackColor()
sceneView.allowsCameraControl = true
let gizmo = GizmoNode()
scene.rootNode.addChildNode(gizmo)
XCPlaygroundPage.currentPage.liveView = sceneView
// Create reference node
let cubeSize = CGFloat(0.5)
let cubeGeometry = SCNBox(width: cubeSize, height: cubeSize, length: cubeSize, chamferRadius: 0.0)
let referenceNodeStandIn = SCNNode(geometry: cubeGeometry)
//referenceNodeStandIn?.load()
let cubeColor = NSColor.whiteColor().colorWithAlphaComponent(0.5)
cubeGeometry.firstMaterial!.diffuse.contents = cubeColor
// Scale reference node
let scale = CGFloat(8)
referenceNodeStandIn.scale = SCNVector3(x: scale, y: scale, z: scale)
// Create geometry for sphere
let sphereRadius = CGFloat(2.0)
let sphereGeometry = SCNSphere(radius: sphereRadius)
//sphereGeometry.materials = gridSphere.geometry!.materials
let gPurpleColor = NSColor.purpleColor().colorWithAlphaComponent(1.0)
sphereGeometry.firstMaterial!.diffuse.contents = gPurpleColor
// Create sphere to hold reference node, position at same place as <gridSphere>
let liveSphere = SCNNode(geometry: sphereGeometry)
//liveSphere.position = gridSphere.position
scene.rootNode.addChildNode(liveSphere)
// Center reference node inside <liveSphere>
var min = SCNVector3Zero
var max = SCNVector3Zero
referenceNodeStandIn.getBoundingBoxMin(&min, max: &max)
print("min: \(min) max: \(max)")
let referenceNodeHeight = max.y - min.y
//referenceNodeStandIn.position = SCNVector3(x: 0, y: 0 - referenceNodeHeight, z: 0)
// Add reference node to <liveSphere>
liveSphere.addChildNode(referenceNodeStandIn)
// This value never changes no matter the scale value because it's in local coordinate space
print("reference node height", referenceNodeHeight)

Related

Cannot Display Sprites Above SK3DNode

I'm displaying some basic 3D geometry within my SpriteKit scene using an instance of SK3DNode to display the contents of a SceneKit scene, as explained in Apple's article here.
I have been able to position the node and 3D contents as I want using SceneKit node transforms and the position/viewport size of the SK3DNode.
Next, I want to display some other sprites in my SpriteKit scene overlaid on top of the 3D content, but I am unable to do so: The contents of the SK3DNode are always drawn on top.
I have tried specifying the zPosition property of both the SK3DNode and the SKSpriteNode, to no avail.
From Apple's documentation on SK3DNode:
Use SK3DNode objects to incorporate 3D SceneKit content into a
SpriteKit-based game. When SpriteKit renders the node, the SceneKit
scene is animated and rendered first. Then this rendered image is
composited into the SpriteKit scene. Use the scnScene property to
specify the SceneKit scene to be rendered.
(emphasis mine)
It is a bit ambiguous withv regard to z-order (it only seems to mention the temporal order in which rendering takes place).
I have put together a minimal demo project on GitHub; the relevant code is:
1. SceneKit Scene
import SceneKit
class SceneKitScene: SCNScene {
override init() {
super.init()
let box = SCNBox(width: 10, height: 10, length: 10, chamferRadius: 0)
let material = SCNMaterial()
material.diffuse.contents = UIColor.green
box.materials = [material]
let boxNode = SCNNode(geometry: box)
boxNode.transform = SCNMatrix4MakeRotation(.pi/2, 1, 1, 1)
self.rootNode.addChildNode(boxNode)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
2. SpriteKit Scene
import SpriteKit
class SpriteKitScene: SKScene {
override init(size: CGSize) {
super.init(size: size)
// Scene Background
self.backgroundColor = .red
// 3D Node
let objectNode = SK3DNode(viewportSize: size)
objectNode.scnScene = SceneKitScene()
addChild(objectNode)
objectNode.position = CGPoint(x: size.width/2, y: size.height/2)
let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
objectNode.pointOfView = cameraNode
objectNode.pointOfView?.position = SCNVector3(x: 0, y: 0, z: 60)
objectNode.zPosition = -100
// 2D Sprite
let sprite = SKSpriteNode(color: .yellow, size: CGSize(width: 250, height: 60))
sprite.position = objectNode.position
sprite.zPosition = +100
addChild(sprite)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
...And the rendered result is:
(I want the yellow rectangle above the green box)
I made a Technical Support Incident with Apple about this and they just got back to me. The solution is actually very very simple.
If you want 2D sprites to render on top of SK3DNodes, you need to stop the contents of the SK3DNodes from writing to the depth buffer.
To do this, you just need to set writesToDepthBuffer to false on the SCNMaterial.
...
let material = SCNMaterial()
material.diffuse.contents = UIColor.green
material.writesToDepthBuffer = false
...
Boom. Works.
Please note that this is just something I stumbled upon. I have no idea why it works and I probably wouldn't trust it without further understanding, but maybe it'll help find an explanation or a real solution.
It seems that having an SKShapeNode (with a fill) alongside an SK3DNode (either as a sibling, part of a sibling tree, or child), draws it in proper order. The SKShapeNode doesn't seem to need to intersect with the SK3DNode either.
The fill is important, as having a transparent fill makes this not work. Stroke doesn't seem to have any effect.
An SKShapeNode of extremely small size and almost zero alpha fill works too.
Here's my playground:
import PlaygroundSupport
import SceneKit
import SpriteKit
let viewSize = CGSize(width: 300, height: 150)
let viewportSize: CGFloat = viewSize.height * 0.75
func skview(color: UIColor, index: Int) -> SKView {
let scene = SKScene(size: viewSize)
scene.backgroundColor = color
let view = SKView(
frame: CGRect(
origin: CGPoint(x: 0, y: CGFloat(index) * viewSize.height),
size: viewSize
)
)
view.presentScene(scene)
view.showsDrawCount = true
// Draw the box of the 3d node view port
let viewport = SKSpriteNode(color: .orange, size: CGSize(width: viewportSize, height: viewportSize))
viewport.position = CGPoint(x: viewSize.width / 2, y: viewSize.height / 2)
scene.addChild(viewport)
return view
}
func cube() -> SK3DNode {
let mat = SCNMaterial()
mat.diffuse.contents = UIColor.green
let box = SCNBox(width: viewportSize, height: viewportSize, length: viewportSize, chamferRadius: 0)
box.firstMaterial = mat
let boxNode3d = SCNNode(geometry: box)
boxNode3d.runAction(.repeatForever(.rotateBy(x: 10, y: 10, z: 10, duration: 10)))
let scene = SCNScene()
scene.rootNode.addChildNode(boxNode3d)
let boxNode2d = SK3DNode(viewportSize: CGSize(width: viewportSize, height: viewportSize))
boxNode2d.position = CGPoint(x: viewSize.width / 2, y: viewSize.height / 2)
boxNode2d.scnScene = scene
return boxNode2d
}
func shape() -> SKShapeNode {
let shape = SKShapeNode(rectOf: CGSize(width: viewSize.height / 4, height: viewSize.height / 4))
shape.strokeColor = .clear
shape.fillColor = .purple
return shape
}
func rect(_ color: UIColor) -> SKSpriteNode {
let sp = SKSpriteNode(texture: nil, color: color, size: CGSize(width: 200, height: viewSize.height / 4))
sp.position = CGPoint(x: viewSize.width / 2, y: viewSize.height / 2)
return sp
}
// The original issue, untouched.
func v1() -> SKView {
let v = skview(color: .red, index: 0)
v.scene?.addChild(cube())
v.scene?.addChild(rect(.yellow))
return v
}
// Shape added as sibling after the 3d node. Notice that it doesn't overlap the SK3DNode.
func v2() -> SKView {
let v = skview(color: .blue, index: 1)
v.scene?.addChild(cube())
v.scene?.addChild(shape())
v.scene?.addChild(rect(.yellow))
return v
}
// Shape added to the 3d node.
func v3() -> SKView {
let v = skview(color: .magenta, index: 2)
let box = cube()
box.addChild(shape())
v.scene?.addChild(box)
v.scene?.addChild(rect(.yellow))
return v
}
// 3d node added after, but zPos set to -1.
func v4() -> SKView {
let v = skview(color: .cyan, index: 3)
v.scene?.addChild(shape())
v.scene?.addChild(rect(.yellow))
let box = cube()
box.zPosition = -1
v.scene?.addChild(box)
return v
}
// Shape added after the 3d node, but not as a sibling.
func v5() -> SKView {
let v = skview(color: .green, index: 4)
let parent = SKNode()
parent.addChild(cube())
parent.addChild(rect(.yellow))
v.scene?.addChild(parent)
v.scene?.addChild(shape())
return v
}
let container = UIView(frame: CGRect(origin: .zero, size: CGSize(width: viewSize.width, height: viewSize.height * 5)))
container.addSubview(v1())
container.addSubview(v2())
container.addSubview(v3())
container.addSubview(v4())
container.addSubview(v5())
PlaygroundPage.current.liveView = container
TL;DR
In your code, try:
...
let shape = SKShapeNode(rectOf: CGSize(width: 0.01, height: 0.01))
shape.strokeColor = .clear
shape.fillColor = UIColor.black.withAlphaComponent(0.01)
// 3D Node
let objectNode = SK3DNode(viewportSize: size)
objectNode.addChild(shape)
...

How to use a SKScene as a SCNMaterial?

I am having a SKScene as a SCNMaterial that I want to apply on a SCNSphere. I want to add a point on the surface of the sphere, that should be partially visible even when it is on the other side of the sphere. So I want a double sided texture. But when I use spherematerial.isDoubleSided = true I get a whitish layer on top of my sphere. Not sure why this happens.
Here is my code:
let surfacematerial = SKScene(size: CGSize(width: 800, height: 400))
surfacematerial.backgroundColor = SKColor.blue
self.point.fillColor = SKColor.red
self.point.lineWidth = 0
self.point.position = CGPoint(x: 200, y: 200)
surfacematerial.addChild(point)
let spherematerial = SCNMaterial()
spherematerial.isDoubleSided = true
spherematerial.diffuse.contents = surfacematerial
let sphere = SCNSphere(radius: CGFloat(2))
sphere.materials = [spherematerial]
spherenode = SCNNode(geometry: sphere)
spherenode.position = SCNVector3(x: 0, y: 0, z: 0)
spherenode.opacity = CGFloat(0.9)

How to add Shadow on surface plane?

I Have one problem, In ARView Display Shadow on the object but did not display on the surface, I have attached try code? It is working Display shadow on the object but does not display on the surface.
Show Image
Code :
var light2 = SCNLight()
var lightNodeb2 = SCNNode()
light2.castsShadow = true
light2.automaticallyAdjustsShadowProjection = true
light2.maximumShadowDistance = 40.0
light2.orthographicScale = 1
light2.type = .directional
light2.shadowMapSize = CGSize(width: 2048, height: 2048)
light2.shadowMode = .forward
light2.shadowSampleCount = 128
light2.shadowRadius = 3
light2.shadowBias = 32
light2.zNear=1;
light2.zFar=1000;
lightNodeb2.light = light2
lightNodeb2.position = SCNVector3(x: -1, y: 10, z: 1)
lightNodeb2.rotation = SCNVector4Make(2, 0, 0, -Float.pi / 3)
self.sceneView.scene.rootNode.addChildNode(lightNodeb2)
I see here 2 possible problems:
Your light's settings are OK. I think the first problem is following: you use programmatic light with "non-programmatic" 3D objects in Scene graph.
Check it. In this code all objects are programmatic:
let sphereNode1 = SCNNode(geometry: SCNSphere(radius: 1))
sphereNode1.position = SCNVector3(x: 0, y: 5, z: 3)
scene.rootNode.addChildNode(sphereNode1)
let sphereNode2 = SCNNode(geometry: SCNSphere(radius: 3))
sphereNode2.position = SCNVector3(x: 0, y: 1, z: 0)
scene.rootNode.addChildNode(sphereNode2)
let boxNode = SCNNode(geometry: SCNBox(width: 20, height: 0.1, length: 20, chamferRadius: 0))
boxNode.position = SCNVector3(x: 0, y: -3, z: 0)
scene.rootNode.addChildNode(boxNode)
let light2 = SCNLight()
let lightNodeb2 = SCNNode()
light2.castsShadow = true
light2.automaticallyAdjustsShadowProjection = true
light2.maximumShadowDistance = 40.0
light2.orthographicScale = 1
light2.type = .directional
light2.shadowMapSize = CGSize(width: 2048, height: 2048)
light2.shadowMode = .deferred // Renders shadows in a postprocessing pass
light2.shadowSampleCount = 128
light2.shadowRadius = 3
light2.shadowBias = 32
light2.zNear = 1
light2.zFar = 1000
lightNodeb2.light = light2
// DIRECTIONAL LIGHT POSITION doesn't matter. Matters only its direction.
// lightNodeb2.position = SCNVector3(x: -1, y: 10, z: 1)
lightNodeb2.rotation = SCNVector4(2, 0, 0, -Float.pi/3)
scene.rootNode.addChildNode(lightNodeb2)
All objects Shadow castings are On by default.
First problem's Solution:
To make your 3D model to become accessible for virtual programmatic lights use the following approach:
func childNode(withName name: String, recursively: Bool) -> SCNNode? {
return SCNNode()
}
let geometryNode = childNode(withName: "art.scnassets/your3Dmodel",
recursively: true)!
scene.rootNode.addChildNode(geometryNode)
Second problem's Solution:
And if you wanna have a hidden plane with a shadow use this code:
hiddenPlaneNode.castsShadow = false
hiddenPlaneNode.geometry?.firstMaterial?.lightingModel = .constant
hiddenPlaneNode.geometry?.firstMaterial?.isDoubleSided = true
hiddenPlaneNode.geometry?.firstMaterial?.readsFromDepthBuffer = true
hiddenPlaneNode.geometry?.firstMaterial?.writesToDepthBuffer = true
hiddenPlaneNode.geometry?.firstMaterial?.colorBufferWriteMask = []
light2.shadowMode = .deferred

Rotate the Scene using panGestureRecognizer

I'm a new programmer working on a simple demo iOS program which I use SceneKit to render the scene.
I want to rotate the camera to see different perspective of the scene. But the original camera control is little bit tricky. Especially when I want to rotate the scene in one direction. e.g. if I swipe from right to left and the camera goes from right to left, then go downwards, then go upwards, then go downwards again, finally it goes left again.
What I want to achieve is that when I swipe from right to the left, the camera rotate around the z-axis. And when I swipe up and down my camera just move up and down. And I can also zoom in and out. Additionally I want to set a constraint so that the camera won't go underground.
So I come up with an idea that I fix the camera to look at the object that I focus on. Then place the object in the center of the scene. And I want to rotate the scene just around z-axis when I swipe the screen.
I assume that I use panGestureRecognizer and translate the position change to a rotation order.But how can I achieve that?
Or you have some other idea to get this results?
Here's a simple version of what I write so far. I've tried using UIPanGestureRecognizer but it didn't work, so I delete it and set allowCameraControl = true
let sceneView = SCNView(frame: self.view.frame)
self.view.addSubview(sceneView)
let scene = SCNScene()
sceneView.scene = scene
let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: -5.0, y: 5.0, z: 5.0)
let light = SCNLight()
light.type = SCNLight.LightType.spot
light.spotInnerAngle = 30
light.spotOuterAngle = 80
light.castsShadow = true
let lightNode = SCNNode()
lightNode.light = light
lightNode.position = SCNVector3(x: 1.5, y: 1.5, z: 1.5)
let ambientLight = SCNLight()
ambientLight.type = SCNLight.LightType.ambient
ambientLight.color = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1.0)
cameraNode.light = ambientLight
let cubeGeometry = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
let cubeNode = SCNNode(geometry: cubeGeometry)
let planeGeometry = SCNPlane(width: 50.0, height: 50.0)
let planeNode = SCNNode(geometry: planeGeometry)
planeNode.eulerAngles = SCNVector3(x: GLKMathDegreesToRadians(-90), y: 0, z: 0)
planeNode.position = SCNVector3(x: 0, y: -0.5, z: 0)
cameraNode.position = SCNVector3(x: -3.0, y: 3.0, z: 3.0)
let constraint = SCNLookAtConstraint(target: cubeNode)
constraint.isGimbalLockEnabled = true
cameraNode.constraints = [constraint]
lightNode.constraints = [constraint]
scene.rootNode.addChildNode(lightNode)
scene.rootNode.addChildNode(cameraNode)
scene.rootNode.addChildNode(cubeNode)
scene.rootNode.addChildNode(planeNode)
sceneView.allowsCameraControl = true
I solved this problem by using Spherical coordinate system.
General idea is to get the start position and the angle, then transform it to spherical coordinate system. Then you do rotation by just changing the two angle.
It's even easier to use spherical coordinate system to deal with pinch gesture. You just have to change the radius.
here's some code that I use.
func handlePan(_ gestureRecognize: UIPanGestureRecognizer) {
// retrieve scene
let carView = self.view as! SCNView
// save node data and pan gesture data
let cameraNode = carView.scene?.rootNode.childNode(withName: "Camera", recursively: true)!
//let translation = gestureRecognize.translation(in: gestureRecognize.view!)
let speed = gestureRecognize.velocity(in: gestureRecognize.view!)
var speedX = sign(Float(speed.x)) * Float(sqrt(abs(speed.x)))
var speedY = sign(Float(speed.y)) * Float(sqrt(abs(speed.y)))
if speedX.isNaN {speedX = 0}
if speedY.isNaN {speedY = 0}
// record start value
let cameraXStart = cameraNode!.position.x
let cameraYStart = cameraNode!.position.y
let cameraZStart = cameraNode!.position.z - 1.0
let cameraAngleStartZ = cameraNode!.eulerAngles.z
let cameraAngleStartX = cameraNode!.eulerAngles.x
let radiusSquare = cameraXStart * cameraXStart + cameraYStart * cameraYStart + cameraZStart * cameraZStart
// calculate delta value
let deltaAngleZ = -0.003 * Float(speedX)
let deltaAngleX = -0.003 * Float(speedY)
// get new Value
var cameraNewAngleZ = cameraAngleStartZ + deltaAngleZ
var cameraNewAngleX = cameraAngleStartX + deltaAngleX
if cameraNewAngleZ >= 100 * Float.pi {
cameraNewAngleZ = cameraNewAngleZ - 100 * Float.pi
} else if cameraNewAngleZ < -100 * Float.pi {
cameraNewAngleZ = cameraNewAngleZ + 100 * Float.pi
} else {
// set limit
if cameraNewAngleX > 1.4 {
cameraNewAngleX = 1.4
} else if cameraNewAngleX < 0.1 {
cameraNewAngleX = 0.1
}
// use angle value to get position value
let cameraNewX = sqrt(radiusSquare) * cos(cameraNewAngleZ - Float.pi/2) * cos(cameraNewAngleX - Float.pi/2)
let cameraNewY = sqrt(radiusSquare) * sin(cameraNewAngleZ - Float.pi/2) * cos(cameraNewAngleX - Float.pi/2)
let cameraNewZ = -sqrt(radiusSquare) * sin(cameraNewAngleX - Float.pi/2) + 1
if cameraNode?.camera?.usesOrthographicProjection == false {
cameraNode?.position = SCNVector3Make(cameraNewX, cameraNewY, cameraNewZ)
cameraNode?.eulerAngles = SCNVector3Make(cameraNewAngleX, 0, cameraNewAngleZ)
}
else if cameraNode?.camera?.usesOrthographicProjection == true {
cameraNode?.position = SCNVector3Make(0, 0, 10)
cameraNode?.eulerAngles = SCNVector3Make(0, 0, cameraNewAngleZ)
}
}
}

Adding an SKLabelNode to Scene Kit View

I am building a game using Scene Kit. In order to present the score I wanted to use an SKLabelNode on the screen, however, when I attach it to a SCNNode, it looks very blurry:
Here is the code that I have written to do this, please let me know if there is a better way to go about doing this without having the text be so blurry. Thank you so much!
func initHUD() {
let skScene = SKScene(size: CGSize(width: 100, height: 100))
skScene.backgroundColor = UIColor(white: 0.0, alpha: 0.0)
labelNode = SKLabelNode()
labelNode.fontSize = 20
labelNode.position.y = 50
labelNode.position.x = 50
skScene.addChild(labelNode)
let plane = SCNPlane(width: 1, height: 1)
let material = SCNMaterial()
material.lightingModelName = SCNLightingModelConstant
material.doubleSided = true
material.diffuse.contents = skScene
plane.materials = [material]
hudNode = SCNNode(geometry: plane)
hudNode.name = "HUD"
hudNode.rotation = SCNVector4(x: 1, y: 0, z: 0, w: 3.14159265)
hudNode.position = SCNVector3(x:0, y: 1, z: -5)
}
func updateHUD() {
labelNode.text = "\(score)"
}
The typical way to do a HUD for a SceneKit scene is to create a SpriteKit SKScene and set it as the overlaySKScene of your SceneKit view. Then it renders at full resolution and always at the same view-relative size and position.

Resources