Accessing all geometries from a .dae file in SceneKit

I'm using SCNMorpher in iOS SceneKit to morph between different shape keys with a model exported as a DAE file from Blender.


My problem is that I get nil whil accessing the geometry of the shape keys (but they are in the .dae file!)


Here's my code:


planeNode.geometry = scene!.rootNode.childNodeWithName("Plane", recursively: true)!.geometry


var morphs: [SCNGeometry] = []
     
let shapeKeys = "Key1 Key2".componentsSeparatedByString(" ")
     
for shapeKey in shapeKeys {
     let changedKey = scene!.rootNode.childNodeWithName(shapeKey, recursively: true)?.geometry
     morphs.append(changedKey!)
}
     
morpher.targets = morphs    

   
planeNode.morpher = morpher


The bug is that the changedKey variable is nil, ie, SceneKit isn't reading the geometry with the name "Key1". I'm able to see the "Plane" node in the viewer of SceneKit but can't find any other Geometries on the tree.


Please see my .dae file for reference:


<?xml version="1.0" encoding="utf-8"?>
<COLLADA xmlns="  <asset>
    <contributor>
      <author>Blender User</author>
      <authoring_tool>Blender 2.77.0</authoring_tool>
    </contributor>
    <unit name="meter" meter="1"/>
    <up_axis>Z_UP</up_axis>
  </asset>
  <library_images/>
  <library_geometries>
    <geometry id="Plane-mesh" name="Plane">
      <mesh>
        <source id="Plane-mesh-positions">
          <float_array id="Plane-mesh-positions-array" count="12">-1 -1 0 1 -1 0 -1 1 0 1 1 0</float_array>
          <technique_common>
            <accessor source="#Plane-mesh-positions-array" count="4" stride="3">
              <param name="X" type="float"/>
              <param name="Y" type="float"/>
              <param name="Z" type="float"/>
            </accessor>
          </technique_common>
        </source>
        <source id="Plane-mesh-normals">
          <float_array id="Plane-mesh-normals-array" count="3">0 0 1</float_array>
          <technique_common>
            <accessor source="#Plane-mesh-normals-array" count="1" stride="3">
              <param name="X" type="float"/>
              <param name="Y" type="float"/>
              <param name="Z" type="float"/>
            </accessor>
          </technique_common>
        </source>
        <vertices id="Plane-mesh-vertices">
          <input semantic="POSITION" source="#Plane-mesh-positions"/>
        </vertices>
        <polylist count="2">
          <input semantic="VERTEX" source="#Plane-mesh-vertices" offset="0"/>
          <input semantic="NORMAL" source="#Plane-mesh-normals" offset="1"/>
          <vcount>3 3 </vcount>
          <p>1 0 2 0 0 0 1 0 3 0 2 0</p>
        </polylist>
      </mesh>
    </geometry>
    <geometry id="Plane-mesh_morph_Key1" name="Key1">
      <mesh>
        <source id="Plane-mesh_morph_Key1-positions">
          <float_array id="Plane-mesh_morph_Key1-positions-array" count="12">-1 -1 0 1 -1 0 -1 1 0 1 2 0</float_array>
          <technique_common>
            <accessor source="#Plane-mesh_morph_Key1-positions-array" count="4" stride="3">
              <param name="X" type="float"/>
              <param name="Y" type="float"/>
              <param name="Z" type="float"/>
            </accessor>
          </technique_common>
        </source>
        <source id="Plane-mesh_morph_Key1-normals">
          <float_array id="Plane-mesh_morph_Key1-normals-array" count="6">0 0 1 0 0 1</float_array>
          <technique_common>
            <accessor source="#Plane-mesh_morph_Key1-normals-array" count="2" stride="3">
              <param name="X" type="float"/>
              <param name="Y" type="float"/>
              <param name="Z" type="float"/>
            </accessor>
          </technique_common>
        </source>
        <vertices id="Plane-mesh_morph_Key1-vertices">
          <input semantic="POSITION" source="#Plane-mesh_morph_Key1-positions"/>
        </vertices>
        <polylist count="2">
          <input semantic="VERTEX" source="#Plane-mesh_morph_Key1-vertices" offset="0"/>
          <input semantic="NORMAL" source="#Plane-mesh_morph_Key1-normals" offset="1"/>
          <vcount>3 3 </vcount>
          <p>1 0 2 0 0 0 1 1 3 1 2 1</p>
        </polylist>
      </mesh>
    </geometry>
    <geometry id="Plane-mesh_morph_Key2" name="Key2">
      <mesh>
        <source id="Plane-mesh_morph_Key2-positions">
          <float_array id="Plane-mesh_morph_Key2-positions-array" count="12">-1 -1 0 1 -1 0 -1 1 0 2 1 0</float_array>
          <technique_common>
            <accessor source="#Plane-mesh_morph_Key2-positions-array" count="4" stride="3">
              <param name="X" type="float"/>
              <param name="Y" type="float"/>
              <param name="Z" type="float"/>
            </accessor>
          </technique_common>
        </source>
        <source id="Plane-mesh_morph_Key2-normals">
          <float_array id="Plane-mesh_morph_Key2-normals-array" count="3">0 0 1</float_array>
          <technique_common>
            <accessor source="#Plane-mesh_morph_Key2-normals-array" count="1" stride="3">
              <param name="X" type="float"/>
              <param name="Y" type="float"/>
              <param name="Z" type="float"/>
            </accessor>
          </technique_common>
        </source>
        <vertices id="Plane-mesh_morph_Key2-vertices">
          <input semantic="POSITION" source="#Plane-mesh_morph_Key2-positions"/>
        </vertices>
        <polylist count="2">
          <input semantic="VERTEX" source="#Plane-mesh_morph_Key2-vertices" offset="0"/>
          <input semantic="NORMAL" source="#Plane-mesh_morph_Key2-normals" offset="1"/>
          <vcount>3 3 </vcount>
          <p>1 0 2 0 0 0 1 0 3 0 2 0</p>
        </polylist>
      </mesh>
    </geometry>
  </library_geometries>
  <library_controllers>
    <controller id="Plane-morph" name="Plane-morph">
      <morph source="#Plane-mesh" method="NORMALIZED">
        <source id="Plane-targets">
          <IDREF_array id="Plane-targets-array" count="2">Plane-mesh_morph_Key1 Plane-mesh_morph_Key2</IDREF_array>
          <technique_common>
            <accessor source="#Plane-targets-array" count="2" stride="1">
              <param name="IDREF" type="IDREF"/>
            </accessor>
          </technique_common>
        </source>
        <source id="Plane-weights">
          <float_array id="Plane-weights-array" count="2">0 0</float_array>
          <technique_common>
            <accessor source="#Plane-weights-array" count="2" stride="1">
              <param name="MORPH_WEIGHT" type="float"/>
            </accessor>
          </technique_common>
        </source>
        <targets>
          <input semantic="MORPH_TARGET" source="#Plane-targets"/>
          <input semantic="MORPH_WEIGHT" source="#Plane-weights"/>
        </targets>
      </morph>
    </controller>
  </library_controllers>
  <library_visual_scenes>
    <visual_scene id="Scene" name="Scene">
      <node id="Plane" name="Plane" type="NODE">
        <matrix sid="transform">1 0 0 0 0 -4.37114e-8 -1 0 0 1 -4.37114e-8 0 0 0 0 1</matrix>
        <instance_geometry url="#Plane-mesh" name="Plane"/>
      </node>
    </visual_scene>
  </library_visual_scenes>
  <scene>
    <instance_visual_scene url="#Scene"/>
  </scene>
</COLLADA>


Any help will be very appreciatted.


Thanks!

Accepted Reply

Hey ksigiscar@cmc! That solution works but it's unfortunate that we have to hide the Nodes... SceneKit should be 'smart enough' to read geometries tag on a COLLADA file. Here is the complete working code:


let scene = SCNScene(named: "square2.dae")
    var planeNode = SCNNode()
    var node1 = SCNNode()
    var node2 = SCNNode()

    let morpher = SCNMorpher()
    var morphs: [SCNGeometry] = [] 
      
    override func viewDidLoad() {
        super.viewDidLoad()
     
        sceneView.scene = scene
        sceneView.allowsCameraControl = true
        sceneView.autoenablesDefaultLighting = true
        sceneView.backgroundColor = UIColor.lightGrayColor()
     
        let urlpath = NSBundle.mainBundle().pathForResource("square2", ofType: "dae")
        let url = NSURL.fileURLWithPath(urlpath!)
        let sceneSource = SCNSceneSource(URL: url, options: nil)
     
        loadGeometriesFromSceneSource(sceneSource!)
     
        node1 = scene!.rootNode.childNodeWithName("Plane1", recursively: true)!
        node1.hidden = true
        node1 = scene!.rootNode.childNodeWithName("Plane2", recursively: true)!
        node1.hidden = true
     
        planeNode = scene!.rootNode.childNodeWithName("BasePlane", recursively: true)!
     
        morpher.targets = morphs
     
        planeNode.morpher = morpher
        print(planeNode.morpher?.targets)
    }


    @IBAction func changePlaneWithShapeKeys() {
        morpher.setWeight(0.5, forTargetAtIndex: 1)
    }

    func loadGeometriesFromSceneSource(sceneSource: SCNSceneSource) {
     
        let identifiers = sceneSource.identifiersOfEntriesWithClass(SCNGeometry.self) as [String]
     
        for identifier in identifiers {
            print(identifier)
            morphs.append(sceneSource.entryWithIdentifier(identifier, withClass: SCNGeometry.self)!)
        }
    }

Thanks for your help!

Replies

And according with "What’s New in Scene Kit Session 500 WWDC 2013" what I'm trying to achieve is possible:


"

Scene Kit exposes the SCNMorpher class to deal with morphing.

All the morphing information and animations can be loaded from a DAE file or you can create everything programmatically.

It's really up to you.

You can also use any kind of geometry for your morph targets as long as their topology matches the one of the base geometry, that is to mean they have the exact same number of vertices and the same triangulation.

"

Have you tried converting the DAE file to an SCN file ? Do you get the same behavior ?

ksigiscar@cmc, it's the same behavior :/


I've used entryWithIdentifier:withClass: to retrieve objects that are not in the scene graph but still only get access to one geometry (the Base geometry):

func loadGeometriesFromSceneSource(sceneSource: SCNSceneSource) {
     let identifiers = sceneSource.identifiersOfEntriesWithClass(SCNGeometry.self) as [String]
     
     for identifier in identifiers {
          print(identifier) // it only gets me the Base Geometry
          morphs.append(sceneSource.entryWithIdentifier(identifier, withClass: SCNGeometry.self)!)
     }
}


Any idea of what am I doing wrong?...

It looks like SceneKit only can see geometries that are assigned to SCNNode instances. There does not seem to be a concept of geometry library.


What you could do is assign a default geometry to the node you want to perform the morph on and have two other nodes (hidden) to which you assign the other geometries. You could then access the geometries of the other two nodes to perform the morph.


Alternatively, try creating the geometries programmatically.

But how to access the geometries and add to the two hidden nodes if SceneKit doesn't recognize them? Are you refering to the dae file? The thing is that the dae file is produced by a 3D model artist and it's a standard 3D file that it should be read by SceneKit. I really want to beleive SceneKit does what the Apple presentation says on "What’s New in Scene Kit Session 500 WWDC 2013":


"

Scene Kit exposes the SCNMorpher class to deal with morphing.

All the morphing information and animations can be loaded from a DAE file or you can create everything programmatically.

It's really up to you.

You can also use any kind of geometry for your morph targets as long as their topology matches the one of the base geometry, that is to mean they have the exact same number of vertices and the same triangulation.

"

When they say "You can also use any kind of geometry for your morph targets as long as their topology matches the one of the base geometry", that does not mean that the target geometries are not already assigned to SCNNode instances.


I don't see anywhere in the SceneKit API where you can access a geometry library, similar to what is in the library_geometries node of your DAE file. I can only see APIs to access the geometry of a given SCNNode. What I meant is that you could have all your morph targets as geometries assigned to SCNNode instances that are not visible, so you can access them in SceneKit. Your artists can create all the morph targets as models. Then you use the isHidden property in SceneKit to hide them from view but use their geometry. In fact, you can get a reference to their geometry then remove them from the scene graph.

I saw mnuages' answer to you on Stack Overflow where he talks about entryWithIdentifier:withClass:


I learnt something new today. Great !

Hey ksigiscar@cmc! That solution works but it's unfortunate that we have to hide the Nodes... SceneKit should be 'smart enough' to read geometries tag on a COLLADA file. Here is the complete working code:


let scene = SCNScene(named: "square2.dae")
    var planeNode = SCNNode()
    var node1 = SCNNode()
    var node2 = SCNNode()

    let morpher = SCNMorpher()
    var morphs: [SCNGeometry] = [] 
      
    override func viewDidLoad() {
        super.viewDidLoad()
     
        sceneView.scene = scene
        sceneView.allowsCameraControl = true
        sceneView.autoenablesDefaultLighting = true
        sceneView.backgroundColor = UIColor.lightGrayColor()
     
        let urlpath = NSBundle.mainBundle().pathForResource("square2", ofType: "dae")
        let url = NSURL.fileURLWithPath(urlpath!)
        let sceneSource = SCNSceneSource(URL: url, options: nil)
     
        loadGeometriesFromSceneSource(sceneSource!)
     
        node1 = scene!.rootNode.childNodeWithName("Plane1", recursively: true)!
        node1.hidden = true
        node1 = scene!.rootNode.childNodeWithName("Plane2", recursively: true)!
        node1.hidden = true
     
        planeNode = scene!.rootNode.childNodeWithName("BasePlane", recursively: true)!
     
        morpher.targets = morphs
     
        planeNode.morpher = morpher
        print(planeNode.morpher?.targets)
    }


    @IBAction func changePlaneWithShapeKeys() {
        morpher.setWeight(0.5, forTargetAtIndex: 1)
    }

    func loadGeometriesFromSceneSource(sceneSource: SCNSceneSource) {
     
        let identifiers = sceneSource.identifiersOfEntriesWithClass(SCNGeometry.self) as [String]
     
        for identifier in identifiers {
            print(identifier)
            morphs.append(sceneSource.entryWithIdentifier(identifier, withClass: SCNGeometry.self)!)
        }
    }

Thanks for your help!

Yes, that would be good if we could just access a library of geometries as defined in the DAE file. Anyway, glad it worked for you.