Up to now I have created multiple new SCNNodes using an instance of SCNGeometry and it was OK that they all had the same appearance. Now I want variety and when I make a copy of that instance using:
let newGeo = myGeoInstance.copy() as! SCNGeometry
(must be force cast because copy() -> any?)
all elements are verified present. :-)
Likewise:
node.geometry?.replaceMaterial(at: index, with: myNewMaterial)
is verified to correctly change the material(s) at the correct index(s). The only problem is the modified "teapot" is not visible, and yes I have set node.isHidden = false.
Has anyone experienced this?
In the old days reversing the verts was a solution. In desperation I tried that. |-(
SceneKit
RSS for tagCreate 3D games and add 3D content to apps using high-level scene descriptions using SceneKit.
Post
Replies
Boosts
Views
Activity
The Actions Editor doesn't seem to work in Xcode 16.1.
With a node selected, when I try to drag an action from the Library into the Actions panel nothing happens, the action icon just disappears. Clicking the '+' button to create a new action doesn't work either.
Actions do work when created in code, though.
Is this a bug, or am I missing something?
I would like to implement zoom functionality in my SceneKit game: when the user performs the pinch gesture on a point on the screen, the scene zooms in to make that point larger.
Until now I simply changed SCNCamera.focalLength, but this simply zooms in to the center of what is currently visible on screen. Is it somehow possible to implement the zoom functionality described above by perhaps interactively rotating the camera at the same time towards the pinched point? Is there a formula for this? I would like to avoid suddenly rotating the camera to face the pinched point when the pinch gesture begins and then zoom in while the pinch is in progress.
I am trying to extract some built-in and custom render passes from SceneKit, so that I can pass them into a metal pipeline and do some additional work with them.
I have a metal viewport, and have instantiated a SCNRenderer so that I can render a SCNScene using SceneKit to a texture as part of my metal draw pass. This works as expected.
Now I want to output multiple textures from the SceneKit render, not just the final color. I want to extract Depth, Normal, Lighting, Colour and a custom SCNTechnique for world position.
I can easily use a SCNTechnique to render one of these to the color output, but it's not clear how I would render multiple passes in one render call.
Is there some way to pass a writeable buffer/texture to a SCNTechnique, so that I can populate it in my SCNTechnique shader at render time with the output from the pass? Similar to how one would bind a buffer for a metal shader. SCNTechnique obfuscates things, so it's not clear how to proceed.
Does anyone have any ideas?
I am working on a SceneKit project where I use a CAShapeLayer as the content for SCNMaterial's diffuse.contents to display a progress bar. Here's my initial code:
func setupProgressWithCAShapeLayer() {
let progressLayer = createProgressLayer()
progressBarPlane?.firstMaterial?.diffuse.contents = progressLayer
DispatchQueue.main.async {
var progress: CGFloat = 0.0
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
progress += 0.01
if progress > 1.0 {
progress = 0.0
}
progressLayer.strokeEnd = progress // Update progress
}
}
}
// MARK: - ARSCNViewDelegate
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
progressBarPlane = SCNPlane(width: 0.2, height: 0.2)
setupProgressWithCAShapeLayer()
let planeNode = SCNNode(geometry: progressBarPlane)
planeNode.position = SCNVector3(x: 0, y: 0.2, z: 0)
node.addChildNode(planeNode)
}
This works fine, and the progress bar updates smoothly. However, when I change the code to use a class property (self.progressLayer) instead of a local variable, the rendering starts flickering on the screen:
func setupProgressWithCAShapeLayer() {
self.progressLayer = createProgressLayer()
progressBarPlane?.firstMaterial?.diffuse.contents = progressLayer
DispatchQueue.main.async { [weak self] in
var progress: CGFloat = 0.0
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] timer in
progress += 0.01
if progress > 1.0 {
progress = 0.0
}
self?.progressLayer?.strokeEnd = progress // Update progress
}
}
}
After this change, the progressBarPlane in SceneKit starts flickering while being rendered on the screen.
My Question:
Why does switching from a local variable (progressLayer) to a class property (self.progressLayer) cause the flickering issue in SceneKit rendering?
I am using SCNTechnique in combination with ARSCNView.
The technique is doing so minor post-processing. I have written several filter variant for this post-processing, but I'm facing an issue when with one of the filters/fragment shaders, SCNTechnique discards my output and just presents the plain camera feed on screen instead. This is clearly visible in the Metal pipeline, using the GPU frame debugger.
Let me stress that my setup works for 90% of my filters, but not this one and I want to know why.
iOS 18.1, iPhone 13 Mini. Xcode 16.1.
Encoder 0 & 1 are injected by the system.
Render encoder 2 & 3 correspond to my SCNTechnique's render passes: one to manipulate pixel data (darken it in this case) and another to BLIT it back to the main texture. I know the separate buffer is not strictly for this particular operation, but it shouldn't matter.
Note that the issue occurs in encoder 4 (not mine but ARKit's).
In Render Encoder 4, scn_postprocess_AR_fragment handle my texture (#0, ending in f980) and another from the camera feed (Texture 2). I know this pass is typically used for grain because that's what it used to do before I disabled grain on ARSCNView (+ the buffer still contains grain paramaters).
I have other post-processing filters that work just fine. By what magic is ARKit determining to use Texture 2 instead of my Texture 0?
Sure, I could keep digging into the minute differences between my shaders to find out which LoC affects how some ARKit shader down the line operates, but it's awfully opaque so far.
I am writing an app to create 3D objects with curved surfaces such as a metal cabinet knob using SceneKit and Model I/O. I want the surfaces to be smooth so that edges between adjacent polygon faces are not visible. According to the documentation for MDLMesh.addNormals(withAttributeNamed: creaseThreshold:), a positive creaseThreshold value lower than 1.0 will interpolate sharper angles between faces into smooth surfaces. I have not been able to get this to work, and I need help with it.
The lines of code where the problem occurs are shown here.
let mesh = MDLMesh(scnGeometry: surfaceGeometry)
// mesh.addNormals(withAttributeNamed: "MDLVertexAttributeNormal", creaseThreshold: 0.9)
surfaceGeometry = SCNGeometry(mdlMesh: mesh)
When the code is executed with middle line commented out, the knob object is rendered as shown in the screenshot. When that line is not commented out, mesh is altered and the SCNNode for the knob is created with no errors, but the node is not rendered.
The questions I have are: (1) What changes do I need the make to the code so that the node will be rendered with a smooth surface?, and (2) what is the recommended way of smoothing a curved surface so that edges between faces are not visible?
The full code for the function and a screenshot of the faceted knob object are attached.
![]("https://developer.apple.com/forums/content/attachment/a17feca7-ed6f-440c-add6-760a1cbf8778" "title=Screenshot cabinet knob with faceted surface.png;width=790;height=568")
code-block
func cabinetKnob() -> SCNNode {
let controlPoints: [(x: Float, y: Float)] = [
(0.728,-0.237), (0.176,-0.06), (0.202,0.475), (0.989,0.842),
(-0.066,1.093), (-0.726,0.787) ]
let pairs = bsplinePath(controlPoints)
var knobProfile = [SCNVector3]()
for (x,y) in pairs {
knobProfile += [ SCNVector3(x: CGFloat(x), y: CGFloat(y), z: 0)]
}
let nProfiles = 64
// create knob by rotating knobProfile about y-axis
let aIncrement: CGFloat = 2 * CGFloat.pi / CGFloat(nProfiles) // ~6 degrees
var angle: CGFloat = 0
var knobVertices = knobProfile.map( { $0 } )
angle = 0
for _ in 1...nProfiles {
angle += aIncrement
// rotate knobProfile about y-axis
knobVertices += knobProfile.map( { $0.rotate(about: .y, by: angle) } )
}
let source = SCNGeometrySource(vertices: knobVertices)
var indices = [[UInt16]]()
var i: UInt16 = 0
var j: UInt16 = UInt16(knobProfile.count) // 1st vertex of next profile
for k in 0...nProfiles {
var stripIndices = [UInt16]()
if k == nProfiles { j = 0 }
for _ in 0...knobProfile.count-1 {
stripIndices += [i, j]
i += 1; j += 1
}
indices += [stripIndices]
}
let elements: [SCNGeometryElement] = indices.map( {
SCNGeometryElement(indices: $0, primitiveType: .triangleStrip) } )
var surfaceGeometry = SCNGeometry(sources: [source], elements: elements)
let mesh = MDLMesh(scnGeometry: surfaceGeometry)
// mesh.addNormals(withAttributeNamed: "MDLVertexAttributeNormal", creaseThreshold: 0.9)
surfaceGeometry = SCNGeometry(mdlMesh: mesh)
let aluminum = SCNMaterial()
aluminum.lightingModel = SCNMaterial.LightingModel.physicallyBased
aluminum.diffuse.contents = NSColor(srgbRed: 0.95, green: 0.95, blue: 0.95, alpha: 1.0)
aluminum.roughness.contents = 0.2
aluminum.metalness.contents = 0.9
aluminum.isDoubleSided = true
surfaceGeometry.materials = [ aluminum ]
let node = SCNNode(geometry: surfaceGeometry)
return node
}
SceneKit SCNScene MacOS 15.1 Xcode 16.0
SceneView(scene: , options:[autoenablesDefaultLighting, allowsCameraContol]
there is:
.rootNode.cameraNode
.rootNode.camera
.rootNode.nestedChildNodes each with its own animation
when the object is animated and dragged by mouse to change the view point, I can't return the view to the previous view.
I have reinstated a clone of the original cameraNode, positions of all childNodes, removed and re-activated all animations... in vain.
I have also cloned, removed and replaced .rootNode.camera, in vain.
The documentation states the camera is "attached" to an SCNNode but does not say how. I make no declaration to associate .rootNode.cameraNode to .rootNode.camera yet if either is absent there is no scene to view.
What am I missing?
Thanks
I would like to preload and use some images for both SpriteKit and SceneKit models (my game uses SceneKit with a SpriteKit overlay), and as far as I can see the only efficient way would be to create and preload SKTexture objects which can be supplied to SKSpriteNode(texture:) and SCNMaterial.diffuse.contents.
The problem is that SKTexture are rendered too bright in SceneKit, for some unknown reason. Here a comparison between rendering an image (from URL) and a SKTexture:
And the code that produces it:
let url = Bundle.main.url(forResource: "art.scnassets/texture.png", withExtension: nil)!
let plane1 = SCNPlane(width: 10, height: 10)
plane1.firstMaterial!.diffuse.contents = url.path
let node1 = SCNNode(geometry: plane1)
node1.position.x = -5
scene.rootNode.addChildNode(node1)
let plane2 = SCNPlane(width: 10, height: 10)
plane2.firstMaterial!.diffuse.contents = SKTexture(image: NSImage(byReferencing: url))
let node2 = SCNNode(geometry: plane2)
node2.position.x = 5
scene.rootNode.addChildNode(node2)
This issue was already mentioned in this other post, but since I wasn't notified of the reply from Quinn asking about the feedback number I created at the time, it didn't make any progress.
I have this minimum repro code:
import SpriteKit
import GameplayKit
class MyGameScene3D: SCNScene {
weak var node3D: MyNode3D!
override init() {
super.init()
background.contents = UIColor.green
let playground = SCNNode()
playground.boundingBox = (
min: SCNVector3(x: 0, y: 0, z: 0),
max: SCNVector3(x: 10, y: 10, z: 10))
let box = SCNNode(geometry: SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0))
box.position = SCNVector3(x: 5, y: 5, z: 5)
playground.addChildNode(box)
playground.position = SCNVector3(x: 0, y: 0, z: 0)
rootNode.addChildNode(playground)
let light = SCNLight()
light.type = .ambient
let lightNode = SCNNode()
lightNode.light = light
rootNode.addChildNode(lightNode)
let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.eulerAngles = SCNVector3(x: -3.14/2, y: 0, z: 0)
cameraNode.position = SCNVector3(x: 5, y: 11, z: 5)
rootNode.addChildNode(cameraNode)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func handleTouchBegan(_ location: CGPoint) {
let res = node3D.hitTest(location)
print(res)
}
}
class MyNode3D: SK3DNode {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
let scene = scnScene as! MyGameScene3D
let location = touch.location(in: self)
print(location)
scene.handleTouchBegan(location)
}
}
class GameScene: SKScene {
init() {
super.init(size: CGSize(width: 500, height: 1000))
self.backgroundColor = .red
let node3D = MyNode3D()
let scene3D = MyGameScene3D()
node3D.scnScene = scene3D
scene3D.node3D = node3D
node3D.isUserInteractionEnabled = true
node3D.viewportSize = CGSize(width: 100, height: 200)
node3D.position = CGPoint(x: 50, y: 100)
addChild(node3D)
let up = SKSpriteNode(color: .blue, size: CGSize(width: 500, height: 10))
up.anchorPoint = CGPoint(x: 0, y:0)
up.position = CGPoint(x:0, y:200)
addChild(up)
let right = SKSpriteNode(color: .gray, size: CGSize(width: 10, height: 500))
right.anchorPoint = CGPoint(x:0,y: 0)
right.position = CGPoint(x:100, y:0)
addChild(right)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Basically, I have a SK3DNode of size 100x200, positioned at lower left corner of the screen (see screenshot below).
Then in this SK3DNode, I have a SCNScene, where I put a 10x10x10 Playground node at position (0, 0, 0). Then I put a camera node right at the top of the Playground at position (5, 11, 5), and the camera looks down along the -y axis, with euler angle = (-90, 0, 0).
Then in this Playground, I put a small box of size 1x1x1, at the center of the Playground at (5, 5, 5).
The 2 long bars (gray & blue) are just there to indicate the boundary of the SK3DNode.
The result rendering is correct (see screenshot below). However, I can't get the hit test working. I tap on the center 1x1x1 box on screen, get the right coordinate printed out, but the hit test result is empty. I want to be get the center 1x1x1 box when hitting there. How can I do so?
Update:
I tried to loop through all the pixels from -2000 to 2000, and still no hit:
func handleTouchBegan(_ location: CGPoint) {
for x in -2000...2000 {
print("handling x: \(x)")
for y in -2000...2000 {
let res = node3D.hitTest(location)
if !res.isEmpty {
print("\(x), \(y), \(res)")
}
}
}
print("Done")
}
I've been running my SceneKit game for many weeks in Xcode without performance issues. The game itself is finished, so I thought I could go on with publishing it on the App Store, but when archiving it in Xcode and running the archived app, I noticed that it seriously hangs.
The hangs only seem to happen when I run the game in fullscreen mode. I tried disabling game mode, but the hangs still happen. Only when I run in windowed mode the game runs smoothly.
Instruments confirms that there are many serious hangs, but it also reports that CPU usage is quite low during those hangs, on average about 15%. From what I know, hangs happen when the main thread is busy, but how can that be when CPU usage is so low, and why does it only happen in fullscreen mode for release builds?
In the simplest case I can come up with, I create a scene (either fully or partially in code) with a single dynamic body, located slightly away from the origin.
I give the body a charge as well as adding an electric field to the node. Body does nothing (as to be expected, since it's the source of the field).
However if I replace that field with a custom field (does nothing except reports back the passed in position value) the position shown is the location of the body in the local space of its parent (in this case, the root node) rather than the node the field is attached to (i.e. itself).
I've attached the code customising the SwiftUI app template. Hopefully someone can tell me what I'm doing wrong?
ContentView customisation…
struct ContentView: View
{
var body: some View
{
SceneView(scene: ElectricScene(), options: [.allowsCameraControl, .autoenablesDefaultLighting])
}
}
And the code to create the scene…
import Foundation
import SceneKit
class ElectricScene: SCNScene
{
override init()
{
super.init()
physicsWorld.gravity = SCNVector3(0, 0, 0)
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 0, 10)
rootNode.addChildNode(cameraNode)
let ballNode = SCNNode(geometry: SCNSphere(radius: 0.5))
ballNode.position = SCNVector3(2, 0, 0)
ballNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
ballNode.physicsBody?.charge = -1
rootNode.addChildNode(ballNode)
// ballNode.physicsField = SCNPhysicsField.electric()
ballNode.physicsField = SCNPhysicsField
.customField {position, _, _, _, _ in
print(position)
return SCNVector3Zero
}
}
@available(*, unavailable)
required init?(coder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
}
This (repeatedly) prints out the following…
SCNVector3(x: 2.0, y: 0.0, z: 0.0)
…which is the position of the node relative to the root node, rather than relative to the source of the field (itself).
Even when the action is run on the main thread, the following code causes a crash on iOS, but not on macOS. The game launches with a simple yellow rectangle, and when it finishes fading out and should be removed from the overlay scene, the app crashes.
The code can be pasted into the file GameController.swift of Xcode's default project for Multiplatform macOS and iOS game.
import SceneKit
import SpriteKit
@MainActor
class GameController: NSObject {
let scene: SCNScene
let sceneRenderer: SCNSceneRenderer
init(sceneRenderer renderer: SCNSceneRenderer) {
sceneRenderer = renderer
scene = SCNScene(named: "Art.scnassets/ship.scn")!
super.init()
sceneRenderer.scene = scene
renderer.overlaySKScene = SKScene(size: CGSize(width: 500, height: 500))
DispatchQueue.main.async {
let node = SKShapeNode(rect: CGRect(x: 100, y: 100, width: 100, height: 100))
node.fillColor = .yellow
node.run(.sequence([
.fadeOut(withDuration: 1),
.removeFromParent()
]))
renderer.overlaySKScene!.addChild(node)
}
}
}
The Xcode console shows this stacktrace:
*** Assertion failure in -[UIApplication _performAfterCATransactionCommitsWithLegacyRunloopObserverBasedTiming:block:], UIApplication.m:3246
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Call must be made on main thread'
*** First throw call stack:
(
0 CoreFoundation 0x00000001804ae0f8 __exceptionPreprocess + 172
1 libobjc.A.dylib 0x0000000180087db4 objc_exception_throw + 56
2 Foundation 0x0000000180d17058 _userInfoForFileAndLine + 0
3 UIKitCore 0x00000001853cf678 -[UIApplication _performAfterCATransactionCommitsWithLegacyRunloopObserverBasedTiming:block:] + 376
4 UIKitCore 0x000000018553f7a0 -[_UIFocusUpdateThrottle scheduleProgrammaticFocusUpdate] + 300
5 UIKitCore 0x0000000184e2e22c -[UIFocusSystem _requestFocusUpdate:] + 548
6 UIKitCore 0x0000000184e2dfa4 -[UIFocusSystem requestFocusUpdateToEnvironment:] + 76
7 UIKitCore 0x0000000184e2e864 -[UIFocusSystem _focusEnvironmentWillDisappear:] + 408
8 SpriteKit 0x00000001a3d472f4 _ZL12_removeChildP6SKNodeS0_P7SKScene + 240
9 SpriteKit 0x00000001a3d473b0 -[SKNode removeChild:] + 80
10 SpriteKit 0x00000001a3d466b8 -[SKNode removeFromParent] + 128
11 SpriteKit 0x00000001a3d1678c -[SKRemove updateWithTarget:forTime:] + 64
12 SpriteKit 0x00000001a3d1b740 _ZN11SKCSequence27cpp_updateWithTargetForTimeEP7SKCNoded + 84
13 SpriteKit 0x00000001a3d20e3c _ZN7SKCNode6updateEdf + 156
14 SpriteKit 0x00000001a3d20f20 _ZN7SKCNode6updateEdf + 384
15 SpriteKit 0x00000001a3d26fb8 -[SKScene _update:] + 464
16 SpriteKit 0x00000001a3cf3168 -[SKSCNRenderer _update:] + 80
17 SceneKit 0x000000019c932bf0 -[SCNMTLRenderContext renderSKSceneWithRenderer:overlay:atTime:] + 60
18 SceneKit 0x000000019c9ebd98 -[SCNRenderer _drawOverlaySceneAtTime:] + 204
19 SceneKit 0x000000019cb1a1c0 _ZN3C3D11OverlayPass7executeERKNS_10RenderArgsE + 60
20 SceneKit 0x000000019c8e05ec _ZN3C3D13__renderSliceEPNS_11RenderGraphEPNS_10RenderPassERtRKNS0_9GraphNodeERPNS0_5StageENS_10RenderArgsEbRPU27objcproto16MTLCommandBuffer11objc_object + 2660
21 SceneKit 0x000000019c8e18ac _ZN3C3D11RenderGraph7executeEv + 3808
22 SceneKit 0x000000019c9ed26c -[SCNRenderer _renderSceneWithEngineContext:sceneTime:] + 756
23 SceneKit 0x000000019c9ed544 -[SCNRenderer _drawSceneWithNewRenderer:] + 208
24 SceneKit 0x000000019c9ed9fc -[SCNRenderer _drawScene:] + 40
25 SceneKit 0x000000019c9edce4 -[SCNRenderer _drawAtTime:] + 500
26 SceneKit 0x000000019ca87950 -[SCNView _drawAtTime:] + 368
27 SceneKit 0x000000019c943b74 __83-[NSObject(SCN_DisplayLinkExtensions) SCN_setupDisplayLinkWithQueue:screen:policy:]_block_invoke + 44
28 SceneKit 0x000000019ca50600 -[SCNDisplayLink _displayLinkCallbackReturningImmediately] + 132
29 libdispatch.dylib 0x000000010239173c _dispatch_client_callout + 16
30 libdispatch.dylib 0x0000000102394c14 _dispatch_continuation_pop + 756
31 libdispatch.dylib 0x00000001023aa4e0 _dispatch_source_invoke + 1736
32 libdispatch.dylib 0x00000001023997f0 _dispatch_lane_serial_drain + 340
33 libdispatch.dylib 0x000000010239a774 _dispatch_lane_invoke + 420
34 libdispatch.dylib 0x00000001023a71a8 _dispatch_root_queue_drain_deferred_wlh + 324
35 libdispatch.dylib 0x00000001023a6604 _dispatch_workloop_worker_thread + 488
36 libsystem_pthread.dylib 0x000000010242bb74 _pthread_wqthread + 284
37 libsystem_pthread.dylib 0x000000010242a934 start_wqthread + 8
)
libc++abi: terminating due to uncaught exception of type NSException
Am I doing something wrong?
Hi Friends!
I’m facing an issue with SceneKit.
I’m developing a 3D mobile game. I have a character 3D model and several skeletal animations CAAnimation. I import both the model and the animations from Maya in *.dae format. The character’s animations play continuously one after the other, with each new animation being triggered randomly. The transition between animations makes smoothly by setting the fadeInDuration and fadeOutDuration properties. Here’s an example of the code:
import UIKit import QuartzCore import SceneKit
class TestAnimationController: UIViewController {
var bodyNode: SCNNode?
override func viewDidLoad() {
super.viewDidLoad()
let scnView = SCNView(frame: self.view.bounds)
scnView.backgroundColor = .black // Set your desired background color
scnView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
let scene = SCNScene(named: "art.scnassets/scene/Base_room/ROOM5.scn")!
bodyNode = collada2SCNNode(filepath: "art.scnassets/female/girl_body_races.dae")!
bodyNode?.renderingOrder = 10
scene.rootNode.addChildNode(bodyNode!)
playIdleAnimation()
scnView.scene = scene // Assign the scene to the SCNView
self.view.addSubview(scnView) // Add the SCNView to your main view)
}
func collada2SCNNode(filepath:String) -> SCNNode? { if let scene = SCNScene(named: filepath) { let node = scene.rootNode.childNodes[0] return node } else { return nil } }
func playIdleAnimation() {
let array = [
"art.scnassets/female/animations/idle/girl_idle_4.dae",
"art.scnassets/female/animations/idle/girl_idle1.dae",
"art.scnassets/female/animations/idle/girl_idle2.dae",
"art.scnassets/female/animations/idle/Girl_idle3.dae",
]
let animation = CAAnimation.animationWithSceneNamed(array.randomElement() ?? "")!
self.setAnimationAdd(
fadeInDuration: 1.0,
fadeOutDuration: 1.0,
keyTime: 0.99,
animation,
isLooped: false
) { [weak self] in
guard let self = self else { return }
try? self.playBoringAnimations()
}
}
func playBoringAnimations() {
let array = [
"art.scnassets/female/animations/boring/girl_boring1.dae",
"art.scnassets/female/animations/boring/girl_boring2.dae",
"art.scnassets/female/animations/boring/girl_boring3.dae",
"art.scnassets/female/animations/boring/girl_boring4.dae",
"art.scnassets/female/animations/boring/girl_boring5.dae",
"art.scnassets/female/animations/boring/girl_boring6.dae",
"art.scnassets/female/animations/boring/girl_boring8.dae"
]
let animation = CAAnimation.animationWithSceneNamed(array.randomElement() ?? "")!
self.setAnimationAdd(
fadeInDuration: 1.0,
fadeOutDuration: 1.0,
keyTime: 0.99,
animation,
isLooped: false
) { [weak self] in
guard let self = self else { return }
try? self.playIdleAnimation()
}
}
func setAnimationAdd(fadeInDuration : CGFloat, fadeOutDuration : CGFloat, keyTime : CGFloat, _ animation: CAAnimation, isLooped: Bool, completion: (() -> Void)?) {
animation.fadeInDuration = fadeInDuration
animation.fadeOutDuration = fadeOutDuration
if !isLooped {
animation.repeatCount = 1
} else {
animation.repeatCount = Float.greatestFiniteMagnitude
}
animation.animationEvents = [
SCNAnimationEvent(keyTime: keyTime, block: { _, _, _ in completion?() })
]
bodyNode?.addAnimation(animation, forKey: "avatarAnimation")
}
}
Everything worked perfectly until I updated to iOS 18. On a physical device, the animations now transition abruptly without the smooth blending that was present in earlier iOS versions. The switch between them is very noticeable, as if the fadeInDuration and fadeOutDuration parameters are being ignored.
However, in the iOS 18 simulator, the animations still transition smoothly as before.
Here two example videos - IOS 17.5 and IOS 18 https://youtube.com/shorts/jzoMRF4skAQ - IOS 17,5 smooth https://youtube.com/shorts/VJXrZzO9wl0 - IOS 18 not smooth
I try this code in IOS 17.5, everything works fine
Does anyone have any ideas on how to fix this issue?
Hi, I’m facing an issue with SceneKit.
I’m developing a 3D mobile game. I have a character 3D model and several skeletal animations CAAnimation. I import both the model and the animations from Maya in *.dae format.
The character’s animations play continuously one after the other, with each new animation being triggered randomly.
The transition between animations makes smoothly by setting the fadeInDuration and fadeOutDuration properties. Here’s an example of the code:
import UIKit
import QuartzCore
import SceneKit
//----------------------
class TestAnimationController: UIViewController {
var bodyNode: SCNNode?
override func viewDidLoad() {
super.viewDidLoad()
let scnView = SCNView(frame: self.view.bounds)
scnView.backgroundColor = .black // Set your desired background color
scnView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
let scene = SCNScene(named: "art.scnassets/scene/Base_room/ROOM5.scn")!
bodyNode = collada2SCNNode(filepath: "art.scnassets/female/girl_body_races.dae")!
bodyNode?.renderingOrder = 10
scene.rootNode.addChildNode(bodyNode!)
playIdleAnimation()
scnView.scene = scene // Assign the scene to the SCNView
self.view.addSubview(scnView) // Add the SCNView to your main view)
}
func collada2SCNNode(filepath:String) -> SCNNode? {
if let scene = SCNScene(named: filepath) {
let node = scene.rootNode.childNodes[0]
return node
} else {
return nil
}
}
func playIdleAnimation() {
let array = [
"art.scnassets/female/animations/idle/girl_idle_4.dae",
"art.scnassets/female/animations/idle/girl_idle1.dae",
"art.scnassets/female/animations/idle/girl_idle2.dae",
"art.scnassets/female/animations/idle/Girl_idle3.dae",
]
let animation = CAAnimation.animationWithSceneNamed(array.randomElement() ?? "")!
self.setAnimationAdd(
fadeInDuration: 1.0,
fadeOutDuration: 1.0,
keyTime: 0.99,
animation,
isLooped: false
) { [weak self] in
guard let self = self else { return }
try? self.playBoringAnimations()
}
}
func playBoringAnimations() {
let array = [
"art.scnassets/female/animations/boring/girl_boring1.dae",
"art.scnassets/female/animations/boring/girl_boring2.dae",
"art.scnassets/female/animations/boring/girl_boring3.dae",
"art.scnassets/female/animations/boring/girl_boring4.dae",
"art.scnassets/female/animations/boring/girl_boring5.dae",
"art.scnassets/female/animations/boring/girl_boring6.dae",
"art.scnassets/female/animations/boring/girl_boring8.dae"
]
let animation = CAAnimation.animationWithSceneNamed(array.randomElement() ?? "")!
self.setAnimationAdd(
fadeInDuration: 1.0,
fadeOutDuration: 1.0,
keyTime: 0.99,
animation,
isLooped: false
) { [weak self] in
guard let self = self else { return }
try? self.playIdleAnimation()
}
}
func setAnimationAdd(fadeInDuration : CGFloat, fadeOutDuration : CGFloat, keyTime : CGFloat, _ animation: CAAnimation, isLooped: Bool, completion: (() -> Void)?) {
animation.fadeInDuration = fadeInDuration
animation.fadeOutDuration = fadeOutDuration
if !isLooped {
animation.repeatCount = 1
} else {
animation.repeatCount = Float.greatestFiniteMagnitude
}
animation.animationEvents = [
SCNAnimationEvent(keyTime: keyTime, block: { _, _, _ in completion?() })
]
bodyNode?.addAnimation(animation, forKey: "avatarAnimation")
}
}
Everything worked perfectly until I updated to iOS 18. On a physical device, the animations now transition abruptly without the smooth blending that was present in earlier iOS versions.
The switch between them is very noticeable, as if the fadeInDuration and fadeOutDuration parameters are being ignored.
However, in the iOS 18 simulator, the animations still transition smoothly as before.
Here two example videos - IOS 17.5 and IOS 18
https://youtube.com/shorts/jzoMRF4skAQ - IOS 17,5 smooth
https://youtube.com/shorts/VJXrZzO9wl0 - IOS 18 not smooth
I am trying to port SceneKit projects to Swift 6, and I just can't figure out how that's possible. I even start thinking SceneKit and Swift 6 concurrency just don't match together, and SceneKit projects should - hopefully for the time being only - stick to Swift 5.
The SCNSceneRendererDelegate methods are called in the SceneKit Thread.
If the delegate is a ViewController:
class GameViewController: UIViewController {
let aNode = SCNNode()
func renderer(_ renderer: any SCNSceneRenderer, updateAtTime time: TimeInterval) {
aNode.position.x = 10
}
}
Then the compiler generates the error "Main actor-isolated instance method 'renderer(_:updateAtTime:)' cannot be used to satisfy nonisolated protocol requirement"
Which is fully understandable.
The compiler even tells you those methods can't be used for protocol conformance, unless:
Conformance is declare as @preconcurrency SCNSceneRendererDelegate like this:
class GameViewController: UIViewController, @preconcurrency SCNSceneRendererDelegate {
But that just delays the check to runtime, and therefore, crash in the SceneKit Thread happens at runtime...
Again, fully understandable.
or the delegate method is declared nonisolated like this:
nonisolated func renderer(_ renderer: any SCNSceneRenderer, updateAtTime time: TimeInterval) {
aNode.position.x = 10
}
Which generates the compiler error: "Main actor-isolated property 'position' can not be mutated from a nonisolated context".
Again fully understandable.
If the delegate is not a ViewController but a nonisolated class, we also have the problem that SCNNode can't be used.
Nearly 100% of the SCNSceneRendererDelegate I've seen do use SCNNode or similar MainActor bound types, because they are meant for that.
So, where am I wrong ? What is the solution to use SceneKit SCNSceneRendererDelegate methods with full Swift 6 compilation ? Is that even possible for now ?
Hello dear forum,
I need to emit an exact amount of particles from a SCNParticleSystem in a burst (this is for an UI effect). This worked for me perfectly in the scene editor by setting the birthrate to the amount and emission duration to 0.
Sadly when I either load such a particle system from a scene or creating it by code, the emitted particles are sometimes one less, or one more.
The first time I run it in the simulator, it seems to work fine but then amount of particles varies as described.
video: https://youtube.com/shorts/MRzqWBy2ypA?feature=share
Does anybody know how to make this predictable?
Thanks so much in advance,
Seb
Every now and then my SceneKit game app crashes and I have no idea why. The SCNView has a overlaySKScene, so it might also be SpriteKit's fault.
The stack trace is
#0 0x0000000241c1470c in jet_context::set_fragment_texture(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, jet_texture*) ()
#27 0x000000010572fd40 in _pthread_wqthread ()
Does anyone have an idea where I could start debugging this, without being able to consistently reproduce it?
I'm trying to update my projects to use Swift6, if I change the project settings to use Swift6 then my app crashes when I add a closure to the SCNAnimation animationDidStop property. The error is inside the SceneKit renderingQueue and indicates that the callback is being called on the wring queue.
Maybe I need to do something in the code to fix this but I can't seem to make it work, maybe a SceneKit bug?
If you create a new game template in Xcode using SceneKit and replace the contents of GameViewController.swift with the following you will see the app crash after it is launched.
import UIKit
import SceneKit
class GameViewController: UIViewController {
let player: SCNAnimationPlayer = {
let a = CABasicAnimation(keyPath: "opacity")
return SCNAnimationPlayer(animation: SCNAnimation(caAnimation: a))
}()
override func viewDidLoad() {
super.viewDidLoad()
let scnView = self.view as! SCNView
scnView.scene = SCNScene()
// Change the project settings to use Swift6
// Setting this closure will then cause a _dispatch_assert_queue_fail
// EXC_BREAKPOINT error in the scenekit.renderingQueue.SCNView queue,
// the only thing on the stack is:
// "%sBlock was %sexpected to execute on queue [%s (%p)]"
player.animation.animationDidStop = { (a: SCNAnimation, b: SCNAnimatable, c: Bool) in
print("stopped")
}
scnView.scene?.rootNode.addAnimationPlayer(player, forKey: nil)
player.play()
}
}
HI guys,
I'm integrating the RoomPlan framework into my app.
I'm able to scan a room and extract the nodes from the CaptureStructure object. So far, I can rebuild the 3D object in the SceneView, but I can't render the openings and the windows correctly. I'm struggling to add these two objects correctly in the wall, in order to make the wall transparent where they are supposed to be.
If I export the CaptureStructure into a usda file and then I load it directly in the SceneView, all the doors, windows and openings are correctly rendered, therefore I do believe that I'm doing something wrong.
Could you please tell me what I'm doing wrong?
I added here a screenshot of my problem:
I have also a prototype, which you can run and see the problem I'm talking about: https://github.com/renanstig/3d-scenekit-prototype