SceneView selective draw since concurrency

I have used SceneKit for several years but recently have a problem where a scene with fewer than 50 nodes is partially drawn, i.e., some nodes are, some aren't, and greater than 50 nodes are always draw correctly. This seems to have happened since concurrency was introduced. (w.r.t. concurrency, I had been using DispatchQueue successfully before then.)

Since all nodes (few or many) are constructed and implemented by the same functions etc. I'm baffled.

When I print the node hierarchy all nodes are present whether few or many. SceneView() has [.rendersContinually] option selected. Every node created (few or many) has .opacity = 1.0, .isHidden = false

I haven't tried setting-back the compiler version as that is not a long term solution, and I know the same code worked fine then.

Answered by G.U.T.explorer in 823634022

Oh my Word guys, the solution was as simple as can be but what a challenge. The line above:

precessionNode.simdTransform = float4x4([Float(0.5.squareRoot()),Float(0.5.squareRoot()),0,0], [-Float(0.5.squareRoot()),Float(0.5.squareRoot()),0,0],[0,0,1,0],[0,0,0,0])

needed to be:

precessionNode.simdTransform = float4x4([Float(0.5.squareRoot()),Float(0.5.squareRoot()),0,0], [-Float(0.5.squareRoot()),Float(0.5.squareRoot()),0,0],[0,0,1,0],[0,0,0,1])

How it got corrupted idk. One little character!

Hello @G.U.T.explorer,

An unexpected visual appearance of a scene is often the result of a coordinate space issue, try implementing some of the debugging strategies described in TN3124: Debugging coordinate space issues.

-- Greg

Thanks for replying Greg

The only thing I hadn't tried in TN3124 was logging .boundingBox, which proved consistent whether few or many nodes. I even tried setting the box (for every childNode) larger to no effect. The parentNode has nil boundingBox.

In playing with the camera options, if .usesOrthographicProjection is false, no nodes appear whether few or many. The default is false per documentation. Yet OP is otherwise not necessary nor desired for my app.

Again, since the common code creates all nodes into a common scene with common camera etc. the only difference I can see is the time required to delete the previous nodes, load-from-file the pattern for the nodes to be drawn. Many nodes take more time than fewer. Yet again, .rendersContinuously is set. |-(

Oh, I almost forgot to cite a potentially important clue. To a view in ContentView I had attached

.onChange(of: scene.rootNode.childNode(withName: "parent-to-few-or-many", recursively: true).childNodes { flushAllChildNodes-reloadFileDescibingChildNodes-reDrawChildNodes }

which had the effect of constantly and alternatively blanking the scene and flashing the desired "fewer" nodes. Proving the childNodes are recognized by SceneKit in the proper position, scale etc. before getting wiped by unknown code. Before you ask, I had also disabled all calls to my own code that removes childNodes just in case poor coding was calling it somewhere. No luck, the fewer were not displayed any the many had subsequent childNodes added for a real mess.

The saga continues. So I thought I'd add sixty dummy childNodes complete with the same geometry as all the other children... just setting isHidden = true. It worked... in part. The unHidden children are not all visible. About 1/3 are. lol

Thanks for applying those debugging strategies!

Can you provide a focused sample project that reproduces the issue you describe?

-- Greg

Let's start with my scene, some of the oldest code and I was just starting coding after many years.

internal func makeAScene() -> SCNScene {
 let firstNode                      = SCNNode()
 let secondNode                = SCNNode()
 firstNode.name                 = “firstNode”
 firstNode.simdPosition      = SIMD3<Float>(0,0,0)
 firstNode.simdTransform   = float4x4([Float(0.5.squareRoot()),Float(0.5.squareRoot()),0,0],[-Float(0.5.squareRoot()),Float(0.5.squareRoot()),0,0],[0,0,1,0],[0,0,0,0])
 secondNode.name            = “secondNode”
 secondNode.simdPosition = SIMD3<Float>(0,0,0)

 let newScene                              = SCNScene()
 newScene.rootNode.addChildNode(firstNode)
 newScene.rootNode.childNode(withName: “firstNode”, recursively: true)?.addChoildNode(secondNode)
 newScene.background.contents = NSColor.black

 newScene.rootNode.camera                                                 = SCNCamera()
 newScene.rootNode.camera?.usesOrthographicProjection  = true
 newScene.rootNode.camera?.orthographicScale                  = 20
 newScene.rootNode.camera?.zNear                                     = -20
 newScene.rootNode.camera?.zFar                                       = 100
 newScene.rootNode.camera?.automaticallyAdjustsZRange = false

return newScene
}

.zNear bothers me because the default is 1 (not a negative number) but doesn't show otherwise. .zFar defaults to 100.

The SceneView is the first view listed within a ZStack{} and is therefore under much Text() and Pickers() etc. The figure constructed by the many elements and drawn in the scene are no wider nor deeper than 10 units in scale. The center of the figure is @ (0,0,0) of the scene's coordinate system.

OK, took a while. Here is a greatly reduced bit of code that should, but does not show my nodes.

// ContentView.swift
// SendToForum
//
// Created on 1/25/25.
//
import SwiftUI
import SceneKit
struct ContentView: View {
@State private var fs_cv = "2"
private let scene = makeAScene()
private struct box: Identifiable {
let radius: Int
let position: [simd_float3]
var id: String { String(radius) }
}
private let boxFlake:[box] = [
box(radius: 0, position: [ simd_float3(0, 0, 0)]),
box(radius: 1, position: [ simd_float3(0, 1, 0),
simd_float3(0, -1, 0),
simd_float3(1, 0, 0),
simd_float3(-1, 0, 0) ]),
box(radius: 2, position: [ simd_float3(0, 2, 0),
simd_float3(0, -2, 0),
simd_float3(2, 0, 0),
simd_float3(-2, 0, 0) ]),
box(radius: 3, position: [ simd_float3(3, 3, 0),
simd_float3(3, -3, 0),
simd_float3(-3, 3, 0),
simd_float3(-3, -3, 0) ])
]
var body: some View {
ZStack(alignment: .topTrailing) {
SceneView(scene: scene, options: [.autoenablesDefaultLighting,
.allowsCameraControl, .rendersContinuously])
Picker(""
, selection: self.$fs_cv) {
let boxFlakeList: [String] = ["0","1","2","3"]
ForEach( boxFlakeList, id: \.self ) { Num in Text(Num) }
}.fixedSize()
.onChange(of: fs_cv, {
let selected_boxFlake:[box] = boxFlake.dropLast( 3 - (fs_cv as
NSString).integerValue )
redraw_animate_scene(boxFlake: selected_boxFlake)
} // .onChange(of: fs_cv
) // .onChange
}
} // var body: some View
private func redraw_animate_scene(boxFlake: [box]) {
for box in boxFlake {
for position in box.position {
position.pNode(drawIntoNode: scene.rootNode.childNode(withName: "Spin",
recursively: true)!, radius: String(box.radius))
}
}
animate(node: scene.rootNode)
} // func redraw_animate_scene()
} // struct ContentView: View
func makeAScene() -> SCNScene {
let precessionNode = SCNNode()
let SpinNode = SCNNode()
precessionNode.name = "precession"
precessionNode.simdPosition = SIMD3<Float>(0,0,0)
precessionNode.simdTransform =
float4x4([Float(0.5.squareRoot()),Float(0.5.squareRoot()),0,0],
[-Float(0.5.squareRoot()),Float(0.5.squareRoot()),0,0],[0,0,1,0],[0,0,0,0])
SpinNode.name = "Spin"
SpinNode.simdPosition = SIMD3<Float>(0, 0, 0)
let newScene = SCNScene()
newScene.rootNode.addChildNode(precessionNode)
newScene.rootNode.childNode(withName: "precession", recursively:
true)?.addChildNode(SpinNode)
// newScene.fogDensityExponent = 0.0
newScene.background.contents = NSColor.black
newScene.rootNode.camera = SCNCamera()
newScene.rootNode.camera?.usesOrthographicProjection = true // @abstract
Determines whether the receiver uses an orthographic projection or not.
Defaults to NO. INVISIBLE WITHOUT IT!
newScene.rootNode.camera?.orthographicScale = 20 // the bigger
the number the smaller the image Without specifying 20 view is too close to
nucleus Defaults to 1.
newScene.rootNode.camera?.zNear = -20 // The default
value is 1.0
newScene.rootNode.camera?.zFar = 100 // The
default value is 100.0
// newScene.rootNode.camera?.fieldOfView = 45 // defaults
to 60 degrees
// newScene.rootNode.camera?.automaticallyAdjustsZRange = true
newScene.rootNode.camera?.automaticallyAdjustsZRange = false /* @discussion
When set to YES, the near and far planes are automatically set to fit the
bounding box of the entire scene at render time. */
return newScene
}
extension simd_float3 {
func pNode(drawIntoNode: SCNNode, radius: String) {
let myBox = SCNBox(width: 0.5, height: 0.5, length: 0.5, chamferRadius: 0.1)
let newNode = SCNNode(geometry: myBox)
newNode.name = "box_\(radius)"
newNode.simdPosition = self
newNode.scale = SCNVector3(x: 1.0, y: 1.0, z: 1.0)
newNode.isHidden = false
newNode.opacity = 1.0 // "A value of 0 means 100%
transparency, while a value of 1 means 100% opacity."
assignMaterials(newNode)
drawIntoNode.addChildNode(newNode)
}
}
func animate(node: SCNNode) {
let precession_action = SCNAction.rotate(by: CGFloat(Double.pi*2), around:
SCNVector3Make(0, 1, 0), duration: 79.17)
let Spin_action = SCNAction.rotate(by: CGFloat(Double.pi*2), around:
SCNVector3Make(0, 1, 0), duration: 4)
let boxSpin_action = SCNAction.rotate(by: -CGFloat(Double.pi*2), around:
SCNVector3Make(0, 1, 0), duration: 0.5)
var childNodeList:[SCNNode] = node.childNodes(passingTest: { (anyNode, stop)
-> Bool in anyNode.name == "precession" } )
for kids in childNodeList {
if !kids.hasActions {
kids.runAction(SCNAction.repeatForever(precession_action)) }
}
childNodeList = node.childNodes(passingTest: { (anyNode, stop) -> Bool in
anyNode.name == "Spin" } )
for kids in childNodeList {
if !kids.hasActions { kids.runAction(SCNAction.repeatForever(Spin_action)) }
}
childNodeList = node.childNodes(passingTest: { (anyNode, stop) -> Bool in
anyNode.name!.hasPrefix("box") } )
for kids in childNodeList {
if !kids.hasActions {
kids.runAction(SCNAction.repeatForever(boxSpin_action)) }
}
}
func assignMaterials(
node: SCNNode) {
_
for (index,_) in zip((node.geometry?.elements.indices)!,
node.geometry!.elements) { // see enumerated() for zero based e.g.,
Array() and ContiguousArray() only else zip(_:_:)
node.geometry?.materials[index].diffuse.contents = NSColor.red }
}
Accepted Answer

Oh my Word guys, the solution was as simple as can be but what a challenge. The line above:

precessionNode.simdTransform = float4x4([Float(0.5.squareRoot()),Float(0.5.squareRoot()),0,0], [-Float(0.5.squareRoot()),Float(0.5.squareRoot()),0,0],[0,0,1,0],[0,0,0,0])

needed to be:

precessionNode.simdTransform = float4x4([Float(0.5.squareRoot()),Float(0.5.squareRoot()),0,0], [-Float(0.5.squareRoot()),Float(0.5.squareRoot()),0,0],[0,0,1,0],[0,0,0,1])

How it got corrupted idk. One little character!

SceneView selective draw since concurrency
 
 
Q