1

I have a free 3d skull like in this photo and code in below:

enter image description here

import UIKit
import SceneKit

class GameViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let scene = SCNScene(named: "skull.scn")!
        
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        scene.rootNode.addChildNode(cameraNode)
        
        cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
        
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light!.type = .omni
        lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
        scene.rootNode.addChildNode(lightNode)
        
        let ambientLightNode = SCNNode()
        ambientLightNode.light = SCNLight()
        ambientLightNode.light!.type = .ambient
        ambientLightNode.light!.color = UIColor.darkGray
        scene.rootNode.addChildNode(ambientLightNode)
        

        let scnView = self.view as! SCNView
        scnView.scene = scene
        scnView.allowsCameraControl = true
        scnView.backgroundColor = UIColor.black
        
        
        let button = UIButton(frame: CGRect(x: 100, y: 600, width: 200, height: 60))
        button.setTitle("Open Jaw", for: .normal)
        button.setTitleColor(.systemBlue, for: .normal)
        
        button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
        
        self.view.addSubview(button)

    }
    
    @objc func buttonAction() {
        print("Button pressed")
        // How can I open jaw here?
    }

}

As you can see in code, I want open the jaw via button, since I have all materials in file, how could I use Dflt2 to do this, I want to know how can I get access to Dflt2 for rotation as opening Jaw with a smooth animation or even changing the color, is it possible?

enter image description here

Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
ios coder
  • 1
  • 4
  • 31
  • 91

1 Answers1

2

Node-centric behaviour

Unlike pro apps, like Autodesk Maya, where through the material you can select the geometry which that material is assigned to, SceneKit doesn't allow you to do that. However, SceneKit, like Maya, has a nodal hierarchical scene structure...

SCNView –> SCNScene -> RootNode 
                           | -> Node_01
                           |       | -> Subnode_A -> Geometry -> Materials
                           |       | -> Subnode_B -> Geometry -> Materials
                           |
                           | -> Node_02 -> Geometry -> Materials
                           |
                           | -> Node_03 -> Geometry -> Materials
                           |
                           | -> Node_04
                           |       | -> Subnode_A
                           |                | -> Subnode_B -> Camera
                           |
                           | -> Node_05 -> SpotLight
                           |
                           | -> Node_06 -> ParticleSystem
                           |
                           | -> Node_07 -> AudioPlayer

...where you can get to desired material only by selecting a specific geometry within a particular node. Such an approach is called node-centric, there's no a material-centric approach, by default. The above abstract diagram shows us the connection of nodes in the scene, where each node has its own transform parameters (translate, rotate and scale).

Thus, you can do what you want only when retrieving a node:

import SceneKit

class GameViewController: UIViewController {
    
    @IBOutlet var sceneView: SCNView!
    let scene = SCNScene(named: "art.scnassets/Skull.usdz")!
    var lowerJaw: SCNNode? = nil
    var counter: Int = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        sceneView.scene = self.scene
        sceneView.allowsCameraControl = true
        sceneView.autoenablesDefaultLighting = true
        sceneView.backgroundColor = .black
        
        guard let skullNode = self.scene.rootNode.childNode(
                                  withName: "Geom", recursively: true)
        else { return }
        
        self.lowerJaw = skullNode.childNodes[0].childNode(
                            withName: "lower_part", recursively: true)
        
        lowerJaw?.childNodes[0].geometry?.firstMaterial?.diffuse.contents = 
                                                         UIColor.systemBlue
    }
    
    @IBAction func pressed(_ sender: UIButton) {
        
        self.counter += 1
        
        let opened = SCNAction.rotateTo(x: .pi/20, y: 0, z: 0, duration: 0.3)
        let closed = SCNAction.rotateTo(x: 0, y: 0, z: 0, duration: 0.3)
        
        if (counter % 2 == 0) {
            self.lowerJaw?.runAction(closed)
        } else {
            self.lowerJaw?.runAction(opened)
        }
    }
}

As you can see, in the Xcode Scene graph, the "Geom" node contains all the parts of the skull, and the "lower_part" subnode, that we're rotating, contains the skull's jaw and lower teeth. So, the most important thing about 3D objects – you need to have separate 3D geometry parts (nodes) in a scene in order to animate them (not a mono-model). The "lower_part" node is rotated around its pivot point. Each SceneKit's node has its own personal pivot. If you need to move the pivot point to another location use this code.

In SceneKit you can use .usdz, .scn, .dae or .obj scenes. Xcode's Scene graph allows you to create, delete, reposition, nest and rename nodes. Nevertheless, the most robust place to build an hierarchy of nodes is 3D authoring app (like Maya or Blender).

enter image description here


Material-centric approach

However, nothing prevents you from creating a collection of dictionaries like [SCNMaterial: SCNNode].

Abstract example (2 spheres, 2 materials):

import SceneKit

let sceneView = SCNView(frame: .zero)
sceneView.scene = SCNScene()

let redMaterial = SCNMaterial()
redMaterial.diffuse.contents = UIColor.red

let greenMaterial = SCNMaterial()
redMaterial.diffuse.contents = UIColor.green

let nodeA = SCNNode(geometry: SCNSphere(radius: 0.1))
nodeA.position.z = -2.5
nodeA.geometry?.materials[0] = redMaterial

let nodeB = SCNNode(geometry: SCNSphere(radius: 0.1))
nodeB.position.y = 1.73
nodeB.geometry?.materials[0] = greenMaterial

sceneView.scene?.rootNode.addChildNode(nodeA)
sceneView.scene?.rootNode.addChildNode(nodeB)

// Dictionary
var getNode = [redMaterial: nodeA, greenMaterial: nodeB]

getNode[redMaterial]?.position.z             // -2.5
getNode[greenMaterial]?.position.y           // 1.73
Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
  • 1
    I am not in your level, but before asking my question I thought that I have to have 2 part in project, one with skull + upper tooth and second with Jaw + down tooth, frankly I did not understand your explaining, is your current answer is what I was thinking? Also I have issue to create Scene graph from your photo, I used "skull.scn" but you used other thing, should I use the extension you showed? – ios coder Apr 04 '22 at 02:15
  • 1
    If we should have 2 parts as I said, it would not be a good approach for other use case, let say I am going to have a human body, and I want open the arms of this human to looks as a cross, if we got 3 parts, like 2 arms and a body without arms, there would be a cut between arm to body, and in would not be a smooth surface in joint area of arm to body, what would be do in that case? this issue was the reason I did not try to have 2 parts in my question. – ios coder Apr 04 '22 at 02:39
  • 1
    Lastly my issue with the answer: I don't know how I can build `SCNScene(named: "art.scnassets/Skull.usdz")` or even other Scene graph, I got `let scene = SCNScene(named: "skull.scn")!` as in my question. Also I got `Dflt2` not sure where is `lower_part` . I upvoted but currently cannot make use of your answer. :( – ios coder Apr 04 '22 at 02:49
  • 1
    First. Yes, my current answer is what you were thinking of. And `.usdz` format behaves the same way as `.scn`. – Andy Jazz Apr 04 '22 at 06:50
  • 1
    Second. 3D skeleton may have 200+ bones, and yes, it's very time consuming to work with the character skeleton (because each joint is a separate node). – Andy Jazz Apr 04 '22 at 06:53
  • 1
    Third. There's no need for you to convert `.scn` scene into `.usdz`, but you can easily do that if you wish – https://stackoverflow.com/questions/50846627/how-to-create-usdz-file-using-xcode-converter/50867018#50867018. Also, as I said earlier, you can add/delete/rename nodes in `Scene graph` pane (you can see `Scene graph` if you open `art.scnassets/` folder and select a `.scn` file in Navigator). – Andy Jazz Apr 04 '22 at 07:08
  • 1
    Thanks again, I will try if i could build the code for myself. – ios coder Apr 04 '22 at 10:59
  • I just tried your given approach, not sure why does not work. I updated my question to showing my tried codes. It would be nice to help me to solve the issue. thanks – ios coder Apr 04 '22 at 14:17
  • 1
    Your `lowerJaw` node equals to `nil`. You should find it among `childNodes` of `"skull.scn"`. – Andy Jazz Apr 04 '22 at 15:32
  • And for what purpose are you using `nodeA` and `nodeB` spheres in your code? I showed them as an abstract example... – Andy Jazz Apr 04 '22 at 15:40
  • 1
    Share your Xcode project via `https://dropmefiles.com`. – Andy Jazz Apr 04 '22 at 15:48
  • 1
    As I said I am not in level to understand or use your help but I know how to use UIKit and Swift codes, my goal is just using single 3D model as in question, and as you said if we must create 2 deferent parts like up and down parts. I want use just single 3d model for that, and I want create those 2 parts with code, rather than importing 2 deferent 3d models. You see, I want use just 1 single 3d model, and create those needed parts with code. Here is the link: https://dropmefiles.com/4bWH1 – ios coder Apr 04 '22 at 20:07
  • 1
    Your model is mono-object (it does not contain separate parts of the model). I will share my Xcode project with the "correct" model. – Andy Jazz Apr 05 '22 at 05:23
  • 1
    Here's a link – https://dropmefiles.com/Ivrbf (activation password `123`) – Do not forget to assign `Development Team` in `Signing and Capabilities` Xcode's tab. – Andy Jazz Apr 05 '22 at 05:37
  • 1
    if you want to animate any 3D model, initially you need a model consisting of several parts. For example, in order to animate the rotation of the wheels of a car, you need to have a 3D model that has a body and 4 wheels separately. – Andy Jazz Apr 05 '22 at 08:39
  • 1
    Thanks Andy, I will try your folder and tell you results. – ios coder Apr 05 '22 at 08:42
  • 1
    If you wanna cut 3D model into parts programmatically – it's `not-worth-it` task. You need a Metal knowledge and many hours of hard work. It is much easier to prepare a "correct" 3D model consisting of the required number of components. 3D modeling - rulez. – Andy Jazz Apr 05 '22 at 08:44
  • 1
    Andy I am getting this error: No idea why it is happening. https://i.stack.imgur.com/AaL1a.png – ios coder Apr 05 '22 at 13:14
  • 1
    Does my Xcode project work? I see it's yours (test8000). Maybe `@IBOutlet` isn't connected to storyboard's SCNView... – Andy Jazz Apr 05 '22 at 13:28
  • 1
    Andy Thanks, that was my funny mistake about `@IBOutlet`. – ios coder Apr 05 '22 at 20:53
  • 1
    Do you have any social media account, if it is okay for you can I have it? like discord or Tiktok, ... – ios coder Apr 05 '22 at 20:54
  • 1
    Nope, I'm just here, and very-very rarely visit LinkedIn. – Andy Jazz Apr 05 '22 at 20:58