I need to subclass a SCNNode
and use the init that takes a MDLObject
(which is not a designated init).
public class MyModelNode: SCNNode {
public var geo: SCNGeometry!
private func dfs(node: SCNNode) {
if let geometry = node.geometry {
geo = geometry
return
}
for child in node.childNodes {
dfs(node: child)
}
}
public init(model: MDLObject) {
super.init(mdlObject: model)
dfs(node: self)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I got error saying that I can't do so, because the init is not a designated init. How can I subclass it but still setup the model object then?
I ended up using DFS to search for the geometry:
import ModelIO
import SceneKit.ModelIO
// Main actor because accessing node.geometry is main isolated
@MainActor
public final class MyModelCache {
public let modelCache: [String: SCNGeometry]
// The model could contain non-geo nodes, such as light and camera.
// We assume there's only 1 geo per model.
private static func dfs(node: SCNNode) -> SCNGeometry? {
if let geometry = node.geometry {
return geometry
}
for child in node.childNodes {
if let ans = dfs(node: child) {
return ans
}
}
return nil
}
public init(bundle: Bundle, models: [String]) {
modelCache = models.reduce(into: [String:SCNGeometry]()) { result, model in
let url = bundle.url(forResource: model, withExtension: nil)!
let asset = MDLAsset(url: url)
asset.loadTextures()
let object = asset.object(at: 0)
let node = SCNNode(mdlObject: object)
guard let geometry = Self.dfs(node: node) else {
fatalError("No geometry found in model file")
}
result[model] = geometry
}
}
public func geometryNamed(_ fn: String) -> SCNGeometry {
// From Apple's doc: The copy shares the underlying vertex data of the original, but can be assigned materials independently.
// Since the model cache is shared, we want to keep it unchanged, so we return a copy.
return modelCache[fn]!.copy() as! SCNGeometry
}
}