Display 3D objects of SceneKit with Swift UI, etc.

Introduction

I had to use SceneKit to display the 3D model. Since the app itself was created with SwiftUI, I will write how to displaySceneKit with SwiftUI. In addition to displaying 3D models, the following will also be covered. --Display View screenshot --Reset display coordinates -Display switching with and without texture

Based on the following articles and source code [Reference] SceneKit to show 3D content in Swift 5 The 3D model data (sneakers) is also from this source.

Display of SceneView

import SwiftUI
import SceneKit

struct SceneKitOnSwiftUI1View: View {
    var body: some View {
        WrappedSceneKit1View()
    }
}

struct WrappedSceneKit1View: UIViewRepresentable {
    
    typealias UIViewType = SCNView
    
    func makeUIView(context: Context) -> SCNView {
        
        let scene = SCNScene(named: "converse_obj.obj")
        let scnView = SCNView()
        scnView.scene = scene

        // 2: Add camera node
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        // 3: Place camera
        cameraNode.position = SCNVector3(x: 0, y: 10, z: 35)
        // 4: Set camera on scene
//        scene.rootNode.addChildNode(cameraNode)
        
        // 5: Adding light to scene
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light?.type = .omni
        lightNode.position = SCNVector3(x: 0, y: 10, z: 35)
        scene?.rootNode.addChildNode(lightNode)
        
        // 6: Creating and adding ambien light to scene
        let ambientLightNode = SCNNode()
        ambientLightNode.light = SCNLight()
        ambientLightNode.light?.type = .ambient
        ambientLightNode.light?.color = UIColor.darkGray
        scene?.rootNode.addChildNode(ambientLightNode)
        
        // Allow user to manipulate camera
        scnView.allowsCameraControl = true
        
        // Show FPS logs and timming
        // sceneView.showsStatistics = true
        
        // Set background color
        scnView.backgroundColor = UIColor.white
        
        // Allow user translate image
        scnView.cameraControlConfiguration.allowsTranslation = false
        
        // Set scene settings
        scnView.scene = scene
        
        return scnView
    }
    func updateUIView(_ uiView: UIViewType, context: Context) {
        
    }
}

What was displayed in UIView of Storyboard in the original source is wrapped in UIViewRepresentable and displayed in SwiftUI. image.png

View View screenshot

I had to create an icon based on the 3D model drawing screen, so that method.

import SwiftUI
import SceneKit

let FINE_NAME = "screenshot.png "

struct SceneKitOnSwiftUI2View: View {
    @State var isScreenShot: Bool = false
    @State var uiImg: UIImage? = nil
    
    var body: some View {
        WrappedSceneKit2View(isScreenShot: self.$isScreenShot, uiImg: self.$uiImg)
            .frame(height: 240)
        Button(action: {
            self.isScreenShot = true
        }){
            VStack {
                Text("ScreenShot")
                if let _uiImg = self.uiImg {
                    Image(uiImage: _uiImg)
                        .resizable()
                        .scaledToFill()
                        .frame(width: 100, height: 100)
                        .clipped()
                }

            }
        }
    }    
}

struct WrappedSceneKit2View: UIViewRepresentable {

    @Binding var isScreenShot: Bool
    @Binding var uiImg: UIImage?

    typealias UIViewType = SCNView
    
    func makeUIView(context: Context) -> SCNView {
        
        //Same as WrappedSceneKit1View
    }

    func updateUIView(_ uiView: UIViewType, context: Context) {
        
        if isScreenShot {
            //Save screenshot
            DispatchQueue.main.async {
                //screenshot
                self.uiImg = uiView.snapshot()
                self.isScreenShot = false
            }
        }
    }
}

The screenshot itself can only be done with uiView.snapshot (). In this source code, a screenshot of View displaying the 3D model at the top of the screen is taken and placed at the bottom of the screen. image.png

Reset display coordinates

By setting allowsCameraControl of SCNView to true, operations such as object rotation can be implemented without writing anything, but there are times when you want to restore it, so reset the display below. I implemented it like this.

import SwiftUI
import SceneKit

struct SceneKitOnSwiftUI3View: View {
    @State var isReset: Bool = false
    var body: some View {
        WrappedSceneKit3View(isReset: self.$isReset)
            .frame(height: 240)
        Button(action: {
            self.isReset = true
        }){
            Text("Reset")
        }
    }
}

struct WrappedSceneKit3View: UIViewRepresentable {
    @Binding var isReset: Bool

    typealias UIViewType = SCNView
    
    func makeUIView(context: Context) -> SCNView {
        let scnView = SCNView()
        self.setup(scnView)        
        return scnView
    }
    func updateUIView(_ uiView: UIViewType, context: Context) {
        self.setup(uiView)
        DispatchQueue.main.async {
            self.isReset = false
        }
    }
    
    func setup(_ scnView: SCNView) {
        let scene = SCNScene(named: "converse_obj.obj")
        scnView.scene = scene

        //Below, makeUIView()Same as what I wrote in
    }
}

setup () is prepared so that it can be initialized at any time.

Display switching with and without texture

Set the texture in code.

import SwiftUI
import SceneKit
import SceneKit.ModelIO


struct SceneKitOnSwiftUI4View: View {
    @State var isColor: Bool = false
    var body: some View {
        WrappedSceneKit4View(isColor: self.$isColor)
            .frame(height: 240)
        Button(action: {
            self.isColor.toggle()
        }){
            Text("Texture")
        }
    }
}

struct WrappedSceneKit4View: UIViewRepresentable {
    @Binding var isColor: Bool

    typealias UIViewType = SCNView
    
    func makeUIView(context: Context) -> SCNView {
        let scnView = SCNView()
        self.setup(scnView)
        
        return scnView
    }
    func updateUIView(_ uiView: UIViewType, context: Context) {
        self.setup(uiView)
        
        //If you paste the object directly on the scene, you can not mess with the texture, so have it as a child node
        let path = Bundle.main.path(forResource: "converse_obj", ofType: "obj")!
        let url = URL(fileURLWithPath: path)
        let modelObj = SCNMaterial()
        let objNode = SCNNode(mdlObject: MDLAsset(url: url).object(at: 0))
        objNode.geometry?.materials = [modelObj]

        //The model is"MDL_OBJ_material0"Since multiple items will be retained under the name, leave them empty once before adding them.
        uiView.scene?.rootNode.childNodes
            .filter({ $0.name == "MDL_OBJ_material0" })
            .forEach{ node in
                node.removeFromParentNode()
            }
        uiView.scene?.rootNode.addChildNode(objNode)

        // diffuse.Specify the texture in contents
        if self.isColor {
            modelObj.diffuse.contents = UIImage(named: "converse.jpg ")
        } else {
            modelObj.diffuse.contents = nil
        }
    }
    
    func setup(_ scnView: SCNView) {
    //Same as WrappedSceneKit3View
    }

If you paste the object directly on Scene, you cannot set the texture, so it is given as a child node.

image.png

Source code: SceneKitOnSwiftUI

Recommended Posts

Display 3D objects of SceneKit with Swift UI, etc.
Cursor display when placing objects with ARKit + SceneKit
Starting with Swift Swift UI
Progress made with Swift UI
How to change BackgroundColor etc. of NavigationBar in Swift UI
Animated display of Swift error messages
Compatible with Android 10 (API 29)
Progress made with Swift UI
Customize View with View Modifier in Swift UI
I tried using Realm with Swift UI
[Swift 5] Implementation of membership registration with Firebase
Shiritori map app made with Swift UI
SKStoreReviewController implementation memo in Swift UI of iOS14
Display around fonts in Japanese with Swift: macOS
[Swift UI] How to disable ScrollsToTop of ScrollView
[Swift] Create an image selection UI with PhotoKit
Incorporate View created with Swift UI into ViewController