Orienting a directional light and adding to scene in ARKit - swift

I have found a few great examples how to add a directional light to my code, but not how to change the orientation as well as add it to my scene. How do I do this with my code? Here is my light class:
class Lighting: Entity, HasDirectionalLight {
required init() {
super.init()
self.light = DirectionalLightComponent(color: .white,
intensity: 100000,
isRealWorldProxy: true)
}
}
And here is the function that calls it:
func addTableToPlane(arView: ARView) {
let tableAnchor = AnchorEntity(plane: .horizontal)
let table = try! Entity.load(named: "Table_1500")
tableAnchor.addChild(table)
let dirLight = Lighting().light
let shadow = Lighting().shadow
tableAnchor.components.set(shadow!)
tableAnchor.components.set(dirLight)
}
I'm a pretty new to ARKit, so I haven't figured out how to edit the orientation of the directional light as I have it.
Another unsuccessful method that I tried was to create a lighting function, but I haven't been able to figure out how to add it to the scene:
func addLights(arView: ARView) {
// 1
let directionalLight = SCNLight()
directionalLight.type = .directional
directionalLight.intensity = 500
// 2
directionalLight.castsShadow = true
directionalLight.shadowMode = .deferred
// 3
directionalLight.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5)
// 4
let directionalLightNode = SCNNode()
directionalLightNode.light = directionalLight
directionalLightNode.rotation = SCNVector4Make(1, 0, 0, -Float.pi / 3)
sceneView.scene.rootNode.addChildNode(directionalLightNode)
}
I then added addLights(arView: uiView) to the addTableToPlane function. I tried to add the light with:
arView.scene.rootNode.addChildNode(ambientLightNode)
but this gives the error that I don't have a childNode and so on. I guess that I'm spoiled with decent docs for Python that supply examples interspersed to help figure out problems, unlike the overly concise docs for Xcode, such as, what the heck I do with "Use the light’s look(at:from:upVector:relativeTo:) method to aim the light". Where do I put this? Where might I find answers to these simple questions?
Chasing my tail for the past couple days just to rotate a light is frustrating.

Use the following code to control orientation of directional light:
Take into consideration that position of Directional Light is not important!
import ARKit
import RealityKit
class Lighting: Entity, HasDirectionalLight, HasAnchoring {
required init() {
super.init()
self.light = DirectionalLightComponent(color: .green,
intensity: 1000,
isRealWorldProxy: true)
}
}
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let light = Lighting()
light.orientation = simd_quatf(angle: .pi/8,
axis: [0, 1, 0])
let boxAnchor = try! Experience.loadBox()
let directLightAnchor = AnchorEntity()
directLightAnchor.addChild(light)
boxAnchor.addChild(directLightAnchor)
boxAnchor.steelBox!.scale = [30,30,30]
boxAnchor.steelBox!.position.z = -3
arView.scene.anchors.append(boxAnchor)
}
}
If you want to know how implement directional light's orientation in SceneKit, read this post.

Related

SceneKit. How to achieve default scene light behavior without autoenablesDefaultLighting?

I tried to set autoenablesDefaultLighting=true for my SCNView and it looks good. However i want to achieve the same behavior without autoenablesDefaultLighting with setting light and adjust it a little bit.
I tried omni light with this code:
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light?.castsShadow = true
lightNode.light?.type = .omni
lightNode.light?.intensity = 10000
lightNode.position = SCNVector3(x: 0, y: 0, z: 100)
scene.rootNode.addChildNode(lightNode)
And got this:
And with autoenablesDefaultLighting=true I got this:
Custom Default Lighting
I believe, in SceneKit, the default scene lighting is a Directional Light without any shadows, attached directly to the default camera node (i.e. pointOfView node). To simulate the same lighting conditions as when the .autoenablesDefaultLighting property is true, use the following code:
Delegate's renderer method – light's position orientation will be updated 60 times per second:
import SceneKit
extension GameViewController: SCNSceneRendererDelegate {
func renderer(_ renderer: SCNSceneRenderer,
updateAtTime time: TimeInterval) {
sunNode.transform = (sceneView?.pointOfView?.worldTransform)!
let cameraAngles = (self.sceneView?.pointOfView?.eulerAngles)!
let lightAngles = self.sunNode.eulerAngles
print("Camera: " + String(format: "%.2f, %.2f, %.2f", cameraAngles.x,
cameraAngles.y,
cameraAngles.z))
print("Light: " + String(format: "%.2f, %.2f, %.2f", lightAngles.x,
lightAngles.y,
lightAngles.z))
}
}
Here's GameViewController class:
class GameViewController: NSViewController {
var sceneView: SCNView? = nil
let sunNode = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
sceneView = self.view as? SCNView
sceneView?.delegate = self
let scene = SCNScene(named: "ship.scn")!
sceneView?.scene = scene
sceneView?.scene?.lightingEnvironment.contents = .none
sceneView?.scene?.background.contents = .none
sceneView?.backgroundColor = .black
sceneView?.allowsCameraControl = true
// sceneView?.autoenablesDefaultLighting = true
sunNode.light = SCNLight()
sunNode.light?.type = .directional
sceneView?.scene?.rootNode.addChildNode(sunNode)
}
}
Explanations
I'd like to add that if there is no light in the scene at all (including the autoenablesDefaultLighting parameter), then the only uncontrollable source of light in the scene will be the non-switchable Ambient Light.
In addition to the above, the Physically Based shader always requires additional Ambient Light fixture (otherwise the physically based surface will be black). The location and orientation of this light source does not matter.
If Directional Light illuminates the surface perpendicularly, then the surface is illuminated with 100% intensity (default intensity is 1000 lumens), but if the rays of the light source are parallel to the surface, then the surface is not illuminated by this source.
As you can see, the first and last images have identical lighting environment.

ARKit – Tap node with raycastQuery instead of hitTest, which is deprecated

In iOS 14, hitTest(_:types:) was deprecated. It seems that you are supposed to use raycastQuery(from:allowing:alignment:) now. From the documentation:
Raycasting is the preferred method for finding positions on surfaces in the real-world environment, but the hit-testing functions remain present for compatibility. With tracked raycasting, ARKit continues to refine the results to increase the position accuracy of virtual content you place with a raycast.
However, how can I hit test SCNNodes with raycasting? I only see options to hit test a plane.
raycastQuery method documentation
Only choices for allowing: are planes
This is my current code, which uses hit-testing to detect taps on the cube node and turn it blue.
class ViewController: UIViewController {
#IBOutlet weak var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
/// Run the configuration
let worldTrackingConfiguration = ARWorldTrackingConfiguration()
sceneView.session.run(worldTrackingConfiguration)
/// Make the red cube
let cube = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0)
cube.materials.first?.diffuse.contents = UIColor.red
let cubeNode = SCNNode(geometry: cube)
cubeNode.position = SCNVector3(0, 0, -0.2) /// 20 cm in front of the camera
cubeNode.name = "ColorCube"
/// Add the node to the ARKit scene
sceneView.scene.rootNode.addChildNode(cubeNode)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
guard let location = touches.first?.location(in: sceneView) else { return }
let results = sceneView.hitTest(location, options: [SCNHitTestOption.searchMode : 1])
for result in results.filter( { $0.node.name == "ColorCube" }) { /// See if the beam hit the cube
let cubeNode = result.node
cubeNode.geometry?.firstMaterial?.diffuse.contents = UIColor.blue /// change to blue
}
}
}
How can I replace let results = sceneView.hitTest(location, options: [SCNHitTestOption.searchMode : 1]) with the equivalent raycastQuery code?
About Hit-Testing
Official documentation says that only ARKit's hitTest(_:types:) instance method is deprecated in iOS 14. However, in iOS 15 you can still use it. ARKit's hit-testing method is supposed to be replaced with a raycasting methods.
Deprecated hit-testing:
let results: [ARHitTestResult] = sceneView.hitTest(sceneView.center,
types: .existingPlaneUsingGeometry)
Raycasting equivalent
let raycastQuery: ARRaycastQuery? = sceneView.raycastQuery(
from: sceneView.center,
allowing: .estimatedPlane,
alignment: .any)
let results: [ARRaycastResult] = sceneView.session.raycast(raycastQuery!)
If you prefer raycasting method for hitting a node (entity), use RealityKit module instead of SceneKit:
let arView = ARView(frame: .zero)
let query: CollisionCastQueryType = .nearest
let mask: CollisionGroup = .default
let raycasts: [CollisionCastHit] = arView.scene.raycast(from: [0, 0, 0],
to: [5, 6, 7],
query: query,
mask: mask,
relativeTo: nil)
guard let raycast: CollisionCastHit = raycasts.first else { return }
print(raycast.entity.name)
P.S.
There is no need to look for a replacement for the SceneKit's hitTest(_:options:) instance method returning [SCNHitTestResult], because it works fine and it's not a time to make it deprecated.

RealityKit - Animate opacity of a ModelEntity?

By setting the color of a material on the model property of a ModelEntity, I can alter the opacity/alpha of an object. But how do you animate this? My goal is to animate objects with full opacity, then have them fade to a set opacity, such as 50%.
With SCNAction.fadeOpacity on a SCNNode in SceneKit, this was particularly easy.
let fade = SCNAction.fadeOpacity(by: 0.5, duration: 0.5)
node.runAction(fade)
An Entity conforms to HasTransform, but that will only allow you to animate scale, position, and orientation. Nothing to do with animation of the material for something like fading it in or out. The effect is in RealityComposer if you create a behavior for animating hide or showing, but there doesn't seem to be something similar to HasTransform to provide functionality for animating opacity.
I've been all around the documentation looking for something, my next idea is essentially creating a custom animation to replace this behavior, but it seems like it should be available and I am just not finding it.
I tested it using different techniques and came to the sad conclusion: you can't animate a material's opacity in RealityKit framework because RealityKit materials don't support animation at runtime (for now I hope). Let's wait for RealityKit's major update.
Here's a code you can use for test
(arView.alpha property just works):
import UIKit
import RealityKit
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
arView.alpha = 1.0
opacityAnimation()
}
func opacityAnimation() {
UIView.animate(withDuration: 5.0,
animations: {
self.arView.alpha = 0.0
})
}
}
And use this code snippet in order to make sure that animation doesn't work properly
(there's no animation process, just value assignment):
import UIKit
import RealityKit
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
let tetheringAnchor = AnchorEntity(world: [0,0,0])
var material = SimpleMaterial()
let mesh: MeshResource = .generateSphere(radius: 0.5)
var sphereComponent: ModelComponent? = nil
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
material.metallic = .float(1.0)
material.roughness = .float(0.0)
material.baseColor = .color(.red)
sphereComponent = ModelComponent(mesh: mesh,
materials: [material])
tetheringAnchor.components.set(sphereComponent!)
arView.scene.anchors.append(tetheringAnchor)
opacityAnimation()
}
func opacityAnimation() {
UIView.animate(withDuration: 5.0,
animations: {
self.material.metallic = .float(1.0)
self.material.roughness = .float(0.0)
self.material.baseColor = .color(.green)
self.sphereComponent = ModelComponent(mesh: self.mesh,
materials: [self.material])
self.tetheringAnchor.components.set(self.sphereComponent!)
self.arView.scene.anchors.append(self.tetheringAnchor)
})
}
}
As #AndyFedo says there is currently no way to animate the opacity nor alpha of an Entity.
Even changing a SimpleMaterial at run time currently results in flickering.
Having said this I was able to animate the Alpha of a SimpleMaterials Color, however based on testing it is in no way optimal or recommended for that matter.
But just in case you wanted to try to further experiment with this avenue please see an attached example which assumes that you only have a single SimpleMaterial:
class CustomBox: Entity, HasModel, HasAnchoring {
var timer: Timer?
var baseColour: UIColor!
//MARK:- Initialization
/// Initializes The Box With The Desired Colour
/// - Parameter color: UIColor
required init(color: UIColor) {
self.baseColour = color
super.init()
self.components[ModelComponent] = ModelComponent(mesh: .generateBox(size: [0.2, 0.2, 0.2]),
materials: [SimpleMaterial (color: baseColour, isMetallic: false)]
)
}
required init() { super.init() }
//MARK:- Example Fading
/// Fades The Colour Of The Entities Current Material
func fadeOut() {
var alpha: CGFloat = 1.0
timer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { timer in
if alpha == 0 {
timer.invalidate()
return
}
var material = SimpleMaterial()
alpha -= 0.01
material.baseColor = MaterialColorParameter.color(self.baseColour.withAlphaComponent(alpha))
material.metallic = .float(Float(alpha))
material.roughness = .float(Float(alpha))
DispatchQueue.main.async {
self.model?.materials = [material]
}
}
}
}
As such just to test you can create and then call the function like so:
let box = CustomBox(color: .green)
box.position = [0,0,-0.5]
arView.scene.anchors.append(box)
box.fadeOut()
Also I would politely ask, that this answer not get downvoted as I am simply iterating the fact that (a) it isn't possible with any current built in methods, and (b) that it can in part be achieved albeit to a very limited extent (and thus currently; in a way which one would see fit for production).
I don't know if it suits with your use case. But you should consider video material.
As you can see in this WWDC session (2min45). An entity with complex pulsating opacity.
https://developer.apple.com/videos/play/wwdc2020/10612/
you can also create the fade in experience in Reality Composer and trigger the .rcproject file in Xcode. Have not tested other interactions with .rcproject but I know at least this can load a model to fade in into the scene.

AR with iOS: putting a light in the scene makes everything black?

Ok, I am trying desperately to achieve this sort of warm lighting on my objects when added to my ARScene in Swift/Xcode - warm lighting and little glowing lights around:
To be clear, I do NOT want the objects I add to my scene to look like they belong in the surrounding room. I want them to stand out/ look warm and glow.All the tutorials on ARKit teach you how to mimic the lighting of the actual room.
Xcode has several lighting options, pulling from the surroundings gathered by the camera because with:
if let lightEstimate = session.currentFrame?.lightEstimate
I can print out the warmth, intensity, etc. And I also have these properties currently set to match the light of room:
sceneView.automaticallyUpdatesLighting = true
extension ARSCNView {
func setup() { //SCENE SETUP
antialiasingMode = .multisampling4X
autoenablesDefaultLighting = true
preferredFramesPerSecond = 60
contentScaleFactor = 1.3
if let camera = pointOfView?.camera {
camera.wantsHDR = true
camera.wantsExposureAdaptation = true
camera.exposureOffset = -1
camera.minimumExposure = -1
camera.maximumExposure = 3
}
}
}
I have tried upping the emission on my object's textures and everything but nothing achieves the effect. Adding a light just turns the objects black/no color.
What is wrong here?
To create this type of glowing red neon light result in ARKit. You can do the following.
You need to create a reactor.scnp (scenekit particle System File) and make the following changes to create the glowing red halo. This should be place in your Resources directory of the playground along with the file spark.png
These are the settings to change from the default reactor type. Leave all the other settings alone.
Change the Image animate color to red/orange/red/black
speed factor = 0.1
enable lighting checked
Emitter Shape = Sphere
Image Size = 0.5
Image Intensity = 0.1
Simulation Speed Factor = 0.1
Note: The code below is playground app I use for testing purposes. You just tap anywhere to add the Neon light into the scene. You can place as many neon lights as you like.
import ARKit
import SceneKit
import PlaygroundSupport
import SceneKit.ModelIO
class ViewController: NSObject {
var sceneView: ARSCNView
init(sceneView: ARSCNView) {
self.sceneView = sceneView
super.init()
self.setupWorldTracking()
self.sceneView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(ViewController.handleTap(_:))))
}
private func setupWorldTracking() {
if ARWorldTrackingConfiguration.isSupported {
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal
configuration.isLightEstimationEnabled = true
self.sceneView.session.run(configuration, options: [])
}
}
#objc func handleTap(_ gesture: UITapGestureRecognizer) {
let results = self.sceneView.hitTest(gesture.location(in: gesture.view), types: ARHitTestResult.ResultType.featurePoint)
guard let result: ARHitTestResult = results.first else {
return
}
let cylinder = SCNCylinder(radius: 0.05, height: 1)
cylinder.firstMaterial?.emission.contents = UIColor.red
cylinder.firstMaterial?.emission.intensity = 1
let spotLight = SCNNode()
spotLight.light = SCNLight()
spotLight.scale = SCNVector3(1,1,1)
spotLight.light?.intensity = 1000
spotLight.castsShadow = true
spotLight.position = SCNVector3Zero
spotLight.light?.type = SCNLight.LightType.directional
spotLight.light?.color = UIColor.white
let particleSystem = SCNParticleSystem(named: "reactor", inDirectory: nil)
let systemNode = SCNNode()
systemNode.addParticleSystem(particleSystem!)
let node = SCNNode(geometry: cylinder)
let position = SCNVector3Make(result.worldTransform.columns.3.x, result.worldTransform.columns.3.y, result.worldTransform.columns.3.z)
systemNode.position = position
node.position = position
self.sceneView.scene.rootNode.addChildNode(spotLight)
self.sceneView.scene.rootNode.addChildNode(node)
self.sceneView.scene.rootNode.addChildNode(systemNode)
}
}
let sceneView = ARSCNView()
let viewController = ViewController(sceneView: sceneView)
sceneView.autoenablesDefaultLighting = false
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = viewController.sceneView
If your looking for a neon/glowing effect in your scene... these previous answers to a similar question asked about glowing/neon lighting should give you some guidance.
As you will see from the answers provided sceneKit does not have built-in support for volumetric lighting, all the approaches are more hacks to achieve a similar effect to a glowing light.
iOS SceneKit Neon Glow
To add a "red" directional light effect to your scene... which is an alternative to using sceneView.autoenablesDefaultLighting = true
let myLight = SCNNode()
myLight.light = SCNLight()
myLight.scale = SCNVector3(1,1,1)
myLight.intensity = 1000
myLight.position = SCNVector3Zero
myLight.light?.type = SCNLight.LightType.directional
myLight.light?.color = UIColor.red
// add the light to the scene
sceneView.scene.rootNode.addChildNode(myLight)
note: This effect makes all the objects in the scene more reddish.

SceneKit SCNSceneRendererDelegate - renderer function not called

I recently asked a question which had a pretty obvious answer. I'm still working on the same project and running into another problem. I need to implement per frame logic and the SCNSceneRendererDelegate protocol worked perfectly fine on iOS, but on OSX, the renderer function is not firing. I have created a little example project to illustrate my problem. It consists of a Scene Kit View in storyboard and following code in the ViewController class:
import Cocoa
import SceneKit
class ViewController: NSViewController, SCNSceneRendererDelegate {
#IBOutlet weak var sceneView: SCNView!
let cubeNode = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
let sphere = SCNSphere(radius: 0.1)
sphere.firstMaterial!.diffuse.contents = NSColor.yellowColor()
let sphereNode = SCNNode(geometry: sphere)
scene.rootNode.addChildNode(sphereNode)
let cube = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0)
cube.firstMaterial!.diffuse.contents = NSColor.greenColor()
cubeNode.geometry = cube
cubeNode.position = SCNVector3(1,0,0)
scene.rootNode.addChildNode(cubeNode)
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(2, 1, 2)
let constraint = SCNLookAtConstraint(target: cubeNode)
cameraNode.constraints = [constraint]
scene.rootNode.addChildNode(cameraNode)
sceneView.scene = scene
sceneView.backgroundColor = NSColor(red: 0.5, green: 0, blue: 0.3, alpha: 1)
sceneView.allowsCameraControl = true
sceneView.delegate = self
sceneView.playing = true
}
func renderer(renderer: SCNSceneRenderer, updateAtTime time: NSTimeInterval) {
cubeNode.position.x += 0.1
}
}
All I want is to basically move the cube with every frame. But nothing happens. What is weird is that when I set sceneView.allowsCameraControl to true, the renderer function is called whenever I click or drag on the screen (which makes sense because it needs to update the view based on camera angles). But I would want it to be called every frame.
Is there an error I don't see or is this a bug in my Xcode?
Edit:
I have tried following the instructions in the answer below and now have the following code for the ViewController:
import Cocoa
import SceneKit
class ViewController: NSViewController {
#IBOutlet weak var sceneView: SCNView!
let scene = MyScene(create: true)
override func viewDidLoad() {
super.viewDidLoad()
sceneView.scene = scene
sceneView.backgroundColor = NSColor(red: 0.5, green: 0, blue: 0.3, alpha: 1)
sceneView.allowsCameraControl = true
sceneView.delegate = scene
sceneView.playing = true
}
}
And a MyScene class:
import Foundation
import SceneKit
final class MyScene: SCNScene, SCNSceneRendererDelegate {
let cubeNode = SCNNode()
convenience init(create: Bool) {
self.init()
let sphere = SCNSphere(radius: 0.1)
sphere.firstMaterial!.diffuse.contents = NSColor.yellowColor()
let sphereNode = SCNNode(geometry: sphere)
rootNode.addChildNode(sphereNode)
let cube = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0)
cube.firstMaterial!.diffuse.contents = NSColor.greenColor()
cubeNode.geometry = cube
cubeNode.position = SCNVector3(1,0,0)
rootNode.addChildNode(cubeNode)
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(2, 1, 2)
let constraint = SCNLookAtConstraint(target: cubeNode)
cameraNode.constraints = [constraint]
rootNode.addChildNode(cameraNode)
}
#objc func renderer(aRenderer: SCNSceneRenderer, updateAtTime time: NSTimeInterval) {
cubeNode.position.x += 0.01
}
}
However, it is still not working. What am I doing wrong?
Edit: setting sceneView.loops = true fixes the described problem
I don't understand what's causing the problem, but I was able to replicate it. I got it to work, though, by adding a meaningless SCNAction:
let dummyAction = SCNAction.scaleBy(1.0, duration: 1.0)
let repeatAction = SCNAction.repeatActionForever(dummyAction)
cubeNode.runAction(repeatAction)
The render loop fires only if the scene is "playing" (see SKScene becomes unresponsive while being idle). I expect that setting
sceneView.isPlaying = true
(as you're already doing) would be enough to trigger the render callbacks.
The code I have above is not a solution. It's a nasty hack to work around your problem and allow you to get on with life.
For anyone still having problems, setting the delegate and playing variables will work.
sceneView.delegate = self
sceneView.isPlaying = true
I suspect the answer hinges on what Querent means by "every frame". Querent should probably clarify this, but I'll try to answer anyway because I'm like that.
The simplest interpretation is probably "every frame that would render anyway", but that seems unlikely to be what is desired unless the cube is intended as a kind of activity monitor for the renderer, which doesn't seem likely either; there are much better approaches to that.
What Querent may want is to render repeatedly while the view's playing property is YES. If that's the case, then perhaps the answer is as simple as setting the view's loops property to YES. This recently solved a problem for me in which I wanted rendering to occur while a keyboard key was held down. (I had noticed that setting playing to YES would induce a single call to my delegate.)
In addition to several helpful hints in this chain, the final one for me to get delegate called was the following: If you use the pre Swift 4 methods for the SCNSceneRendererDelegate class, it compiles fine with no errors or warnings, but the delegate is never called.
Thus the obsolete pre-Swift 4 definition:
func renderer(aRenderer:SCNSceneRenderer, updateAtTime time:TimeInterval) {...}
(which I got from a snippet on the web) compiled just fine and was never called, while the correct definition
func renderer(_ renderer:SCNSceneRenderer, updateAtTimet time:TimeInterval) {...}
compiles and gets called!
Since SCNSceneRendererDelegate is a protocol, the normal Swift protections afforded by override are not appropriate. Since SCNSceneRendererDelegate defines its methods as optional (which I like), it is not caught that way either.
I've been struggling with some similar bugs using SCNRenderer rather than SCNView, and this was the first hit on Google, so I just wanted to add a note with my solution in case it helps anyone.
I was using
.render(withViewport:commandBuffer:passDescriptor:)
but this does not call the delegate render method. Instead, use
.render(atTime:viewport:commandBuffer:passDescriptor:)
even if you are not using the time interval parameter. Then the delegate method renderer(_:updateAtTime:) will be called properly.
Try this…put your code in a scene class instead – keep the view controller clean.
final class MySCNScene:SCNScene, SCNSceneRendererDelegate
{
#objc func renderer(aRenderer:SCNSceneRenderer, updateAtTime time:NSTimeInterval)
{
}
}
Also set the view's delegate to your scene:
mySCNView!.delegate = mySCNScene