admin管理员组

文章数量:1134237

I observed a very strange behavior where a copied SCNGeometry doesn't work immediately. I had to wait one run-loop (DispatchQueue.main.async) in order to make it work. Is it a bug or am I doing something wrong?

Here's my code:

  func helper_cloneGeo(geo: SCNGeometry) -> SCNGeometry {
    let copiedGeo = geo.copy() as! SCNGeometry
    copiedGeo.materials = geo.materials.map { $0.copy() as! SCNMaterial }
    return copiedGeo
  }
  
  override init() {
    super.init()

    let geo = readGeo(fn: "alcohol.usdz", texture: "texture.png").geometry!
    
    // HERE!!!
    // SCNGeometry is only clone-able after async!!
    // DispatchQueue.main.async {
      let copiedGeo = self.helper_cloneGeo(geo: geo)
      let newNode = SCNNode(geometry: copiedGeo)
      self.rootNode.addChildNode(newNode)
    // }
    
  }

The readGeo is just a helper function that reads a 3d model file, which should be correct, because the original (not cloned) node works.

You can download my sample project here:

In this project, when you run it, it shows the model. Then if you comment out the DispatchQueue.main.async (like in the code above), the model won't be shown.

I observed a very strange behavior where a copied SCNGeometry doesn't work immediately. I had to wait one run-loop (DispatchQueue.main.async) in order to make it work. Is it a bug or am I doing something wrong?

Here's my code:

  func helper_cloneGeo(geo: SCNGeometry) -> SCNGeometry {
    let copiedGeo = geo.copy() as! SCNGeometry
    copiedGeo.materials = geo.materials.map { $0.copy() as! SCNMaterial }
    return copiedGeo
  }
  
  override init() {
    super.init()

    let geo = readGeo(fn: "alcohol.usdz", texture: "texture.png").geometry!
    
    // HERE!!!
    // SCNGeometry is only clone-able after async!!
    // DispatchQueue.main.async {
      let copiedGeo = self.helper_cloneGeo(geo: geo)
      let newNode = SCNNode(geometry: copiedGeo)
      self.rootNode.addChildNode(newNode)
    // }
    
  }

The readGeo is just a helper function that reads a 3d model file, which should be correct, because the original (not cloned) node works.

You can download my sample project here: https://drive.google.com/file/d/1kYyqCAJXnXqpZc6vaaXORe_UVmLH6wHL/view?usp=sharing

In this project, when you run it, it shows the model. Then if you comment out the DispatchQueue.main.async (like in the code above), the model won't be shown.

Share Improve this question edited Jan 7 at 18:22 OMGPOP asked Jan 7 at 18:03 OMGPOPOMGPOP 1,0728 gold badges52 silver badges101 bronze badges 4
  • By doing some basic debugging, you can see that geo.elements and geo.sources are all empty, and this is why nothing got copied. I suspect that flattenedClone somehow made a node with a "lazy" geometry. If you don't flattenedClone and just clone node.childNodes.first?.childNodes.first?.geometry. It works as expected. – Sweeper Commented Jan 7 at 19:00
  • Hmmm, i have to flattenedClone it in my real project, so that a node only has 1 geometry which is easier to deal with, regardless of usdz file setup (which i cant control since i buy them in batch from different studios) – OMGPOP Commented Jan 7 at 20:18
  • My point wasn't to offer an alternative in the first place... You are not asking for an alternative, right? Since DispatchQueue.main.async already works. – Sweeper Commented Jan 7 at 20:43
  • Sorry for the confusion, i can't use DispatchQueue.main.async, since i have to add the models in the init of my container node. i could add the child node async'ly, but i have bunch of infras relying on the container being setup properly. I could load the assets on app launch, but that's also quite a huge change due to the scale of my infra – OMGPOP Commented Jan 7 at 23:37
Add a comment  | 

2 Answers 2

Reset to default 1

By doing some basic debugging, you can see that geo.elements and geo.sources are all empty, and this is why nothing got copied. I suspect that SCNNode.flattenedClone (which you used in readGeo) somehow made a node with a "lazy" geometry that doesn't get immediately created.

If you don't want to do this asynchronously, you can always just run the run loop yourself.

let geo = readGeo(fn: "alcohol.usdz", texture: "texture.png").geometry!

RunLoop.main.run(until: .now) // run the run loop for just one iteration

let copiedGeo = self.helper_cloneGeo(geo: geo)
let newNode = SCNNode(geometry: copiedGeo)

Not sure why but this works. Likely related to SceneKit internal implementation, as Sweeper suggested.

func helper_cloneGeo(geo: SCNGeometry) -> SCNGeometry {
  return SCNNode(geometry: geo).clone().geometry!
}

本文标签: iosCopied SCNGeometry does not work immediately in current runloopStack Overflow