swiftcore-animationscenekit

setting scnnode presentation position


I want to set the transform of both a given scn:SCNNode and its current presentation node, to a new value. However, I'm having trouble setting the presentation node. I've tried four ways:

  1. Set presentation node's transform:

    scn.position              = newVal
    scn.presentation.position = newVal
    

    scn.presentation.transform is read only -- it can't be set. (BTW, setting the presentation node's transform compiles with no errors, perhaps something to clean up)

  2. Use resetTransform():

    scn.position              = newVal
    scn.physicsBody.resetTransform()
    

    does nothing. The docs say it "Updates the position and orientation of a body in the physics simulation to match that of the node to which the body is attached". But it isn't changed. Not clear why!

  3. Remove the physicsBody while setting:

    let pb                  = scn.physicsBody
    scn.physicsBody         = nil
    scn.position            = newVal
    scn.physicsBody         = pb
    

    This sets the presentation transform to newVal ("yea!"), but Physics does not work ("boo!"). Perhaps one cannot reuse a physics body.

  4. Add a new physics body after setting:

    scn.position            = newVal
    scn.physicsBody         = SCNPhysicsBody(type:.dynamic, shape:nil)
    

    but alas, scn.presentation.position doesn't have newVal.

Thoughts?


Solution

  • I've been noticing this as well in my SceneKit-using project. The direct answer to your question is: “The presentation node automatically updates its transform to match the scene node's transform. If it doesn't seem to be updating, make sure the scene node's isPaused is set to false.” However, it's likely that your scene nodes are un-paused and/or you're not using animations at all, and yet this issue occurs.

    I started noticing this issue happening intermittently on iOS 11 when everything in my project worked great on iOS 10.  I haven't touched SCNAnimations or anything like that— I have my own animation system, so I just update my nodes' .position every renderer(_:, updateAtTime:).

    So AFAICT, the issue here isn't anything you're doing— the issue is that SceneKit has always been buggy and poorly-written (some parts of the API still haven't been fully updated for Swift), and will likely remain buggy and poorly-written because it seems the SceneKit team has completely moved onto working on ARKit.  So the solution to your problem seems to be “just try a bunch of stuff, setting .position more frequently than necessary, or at a different time in the update loop, or really hack-y solution that seems to work-around the issues in SceneKit”.

    For me, personally, I'm slowly working towards abandoning SceneKit in my project— I've mostly switched to custom Metal shaders, and then I'll do my own scene render loop, and eventually I'll replace the scene graph and SCNNode usage leaving me with zero reliance on the buggy mess that is SceneKit.

    I wish I had better news for you, but I've been dealing with the weirdness of SceneKit for 2 years now.  It's unfinished garbage.

    Update: I ended up “fixing” my presentation nodes misbehaving by using a dirty hack that adds a tiny amount of “wiggle” to the node's position each frame:

    public func updateNode()
    {
        let wigglePositionValue = SCNVector3(
            x: Float.random(in: -0.00001...0.0001),
            y: Float.random(in: -0.00001...0.0001),
            z: Float.random(in: -0.00001...0.0001)
        )
        self.scnNode.position = _locationModel.value + wigglePositionValue
    }
    

    Side-note: As expected, “jiggling” node positions like this also disallows SceneKit from doing some of its batching/rendering optimizations, and (in my experience) incurs a performance hit. It's a horrible hacky work-around, only intended for worst-case scenarios (“the client says we have to go live with the game this week or we don't get paid, and now this stupid SceneKit bug?!”)

    So yeah… the real solution here is “don't use SceneKit”.