Import 3D model in SceneKit - Swift - swift

I'm creating a game where the player can control a cube.
He could buy new cubes.
I created the basic cube like that :
// MAIN CUBE
mainCubeGeometry = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0.0)
mainCubeGeometry.firstMaterial?.diffuse.contents = UIColor.redColor()
mainCubeNode = SCNNode(geometry: mainCubeGeometry)
mainCubeNode.position = SCNVector3(x: 0.0, y: 0.0, z: 0.0)
scene.rootNode.addChildNode(mainCubeNode)
I would like to know how use 3D models (dae) of cubes created on any software (SketchUp per exemple). I don't really understand how to load the 3D file when the scene is created (scene = SCNScene(named: "3d.scnassets/cube.dae")) because it is a scene, so it import a scene into my original scene, while I just want to import a 3D model in a node to use it like my basic cube.
Thanks for your help !

SCNScene(named:) returns an SCNNode with all of the objects from that DAE file as child nodes. Load the file, retrieve the bodies you're interested in, and add them to your scene's root node.
Here are a couple of snippets from the Fox sample app from WWDC 2015. The Character class has a node property initialized like this:
let characterScene = SCNScene(named: "game.scnassets/panda.scn")!
let characterTopLevelNode = characterScene.rootNode.childNodes[0]
node.addChildNode(characterTopLevelNode)
And then add the character to the scene like this:
// Add the character to the scene.
scene.rootNode.addChildNode(character.node)

SOLUTION
I found a solution on this post : How do you load a .dae file into an SCNNode in IOS SceneKit?
class func collada2SCNNode(filepath:String) -> SCNNode {
var node = SCNNode()
let scene = SCNScene(named: filepath)
var nodeArray = scene!.rootNode.childNodes
for childNode in nodeArray {
node.addChildNode(childNode as SCNNode)
}
return node
}
Use :
let myNode = collada2SCNNode("nodes.scnassets/node.dae")
scene.rootNode.addChildNode(myNode)

Related

How to export the contents of a SceneKit scene under MacOS

I'm getting unexpected results when exporting the contents of a SceneKit scene to a Collada (.dae) file. Here's what I have so far.
I created a simple scene with 5 spheres along the x-axis
var x:CGFloat = 0
for i in 0...4 {
let sphere = SCNNode(geometry: SCNSphere(radius: 1))
sphere.name = "sphere\(i+1)"
sphere.position = SCNVector3(x: x, y: 0, z: 0)
exportScene.rootNode.addChildNode(sphere)
x += 2
}
and exported the contents with
let url = URL(fileURLWithPath: pathName)
exportScene.write(to: url, options: nil, delegate: nil) { totalProgress, error, stop in
print("Export progress: \(totalProgress * 100.0)%")
}
When I load the .dae file into a 3D program (Cheetah 3D), I expect to have 5 identical spheres along the x-axis but instead the following appears. I had similar issues exporting to a .obj file.
The answer in the following says "Keep in mind that DAE doesn't handle all features of SceneKit, though" but the doesn't go into the limitations of the file format.
Easiest method to export a SceneKit scene as a Collada .dae file?
Q: Does anyone know how to export the contents of a SceneKit scene?
macOS app
Looks like the beginning of SceneKit's sunset.
Neither .dae nor .obj formats are properly generated in SceneKit macOS app. Moreover, an .usdz format is not exported at all.
iOS app
In iOS app, only the .usdz format is exported correctly (it kept all nodes' transforms and names of the SCN scene). This .usdz can be opened in Maya. But .dae and .obj files contain only one sphere instead of five.
If you have problems with USDZ's textures in Maya, read this post please.
Note that .usdz is not exported correctly when using for-in loop.
import SceneKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let sceneView = self.view as! SCNView
sceneView.backgroundColor = .black
sceneView.scene = SCNScene()
let url = URL(string: "file:///Users/swift/Desktop/model.usdz")!
let sphere1 = SCNNode(geometry: SCNSphere(radius: 0.5))
sphere1.position = SCNVector3(x: -2, y: 0, z: 0)
sceneView.scene!.rootNode.addChildNode(sphere1)
// ...sphere2, sphere3, sphere4...
let sphere5 = SCNNode(geometry: SCNSphere(radius: 0.5))
sphere5.position = SCNVector3(x: 2, y: 0, z: 0)
sceneView.scene!.rootNode.addChildNode(sphere5)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
sceneView.scene?.write(to: url, delegate: nil) { (prgs, _, _) in
print("Export progress: \(prgs * 100.0)%")
}
}
}
}
P. S.
Tested it on macOS 13.0 Ventura, Xcode 14.1, iOS 16.1 Simulator.

ARKit – How to display the feed from a virtual SCNCamera placed on SCNPlane?

I put some objects in AR space using ARKit and SceneKit. That works well. Now I'd like to add an additional camera (SCNCamera) that is placed elsewhere in the scene attached and positioned by a common SCNNode. It is oriented to show me the current scene from an other (fixed) perspective.
Now I'd like to show this additional SCNCamera feed on i.Ex. a SCNPlane (as the diffuse first material) - Like a TV screen. Of course I am aware that it will only display the SceneKit content which stays in the camera focus and not rest of the ARKit image (which is only possible by the main camera of course). A simple colored background then would be fine.
I have seen tutorials that describes, how to play a video file on a virtual display in ARSpace, but I need a realtime camera feed from my own current scene.
I defined this objects:
let camera = SCNCamera()
let cameraNode = SCNNode()
Then in viewDidLoad I do this:
camera.usesOrthographicProjection = true
camera.orthographicScale = 9
camera.zNear = 0
camera.zFar = 100
cameraNode.camera = camera
sceneView.scene.rootNode.addChildNode(cameraNode)
Then I call my setup function to place the virtual Display next to all my AR stuff, position the cameraNode as well (pointing in the direction where objects stay in the scene)
cameraNode.position = SCNVector3(initialStartPosition.x, initialStartPosition.y + 0.5, initialStartPosition.z)
let cameraPlane = SCNNode(geometry: SCNPlane(width: 0.5, height: 0.3))
cameraPlane.geometry?.firstMaterial?.diffuse.contents = cameraNode.camera
cameraPlane.position = SCNVector3(initialStartPosition.x - 1.0, initialStartPosition.y + 0.5, initialStartPosition.z)
sceneView.scene.rootNode.addChildNode(cameraPlane)
Everything compiles and loads... The display shows up at the given position, but it stays entirely gray. Nothing is displayed at all from the SCNCamera I put in the scene. Everything else in the AR scene works well, I just don't get any feed from that camera.
Hay anyone an approach to get this scenario working?
To even better visualize, I add some more print screens.
The following shows the Image trough the SCNCamera according to ARGeo's input. But it takes the whole screen, instead of displaying its contents on a SCNPlane, like I need.
The next Print screen actually shows the current ARView result as I got it using my posted code. As you can see, the gray Display-Plane remains gray - it shows nothing.
The last print screen is a photomontage, showing the expected result, as I'd like to get.
How could this be realized? Am I missing something fundamental here?
After some research and sleep, I came to the following, working solution (including some inexplainable obstacles):
Currently, the additional SCNCamera feed is not linked to a SCNMaterial on a SCNPlane, as it was the initial idea, but I will use an additional SCNView (for the moment)
In the definitions I add an other view like so:
let overlayView = SCNView() // (also tested with ARSCNView(), no difference)
let camera = SCNCamera()
let cameraNode = SCNNode()
then, in viewDidLoad, I setup the stuff like so...
camera.automaticallyAdjustsZRange = true
camera.usesOrthographicProjection = false
cameraNode.camera = camera
cameraNode.camera?.focalLength = 50
sceneView.scene.rootNode.addChildNode(cameraNode) // add the node to the default scene
overlayView.scene = scene // the same scene as sceneView
overlayView.allowsCameraControl = false
overlayView.isUserInteractionEnabled = false
overlayView.pointOfView = cameraNode // this links the new SCNView to the created SCNCamera
self.view.addSubview(overlayView) // don't forget to add as subview
// Size and place the view on the bottom
overlayView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width * 0.8, height: self.view.bounds.height * 0.25)
overlayView.center = CGPoint(x: self.view.bounds.width * 0.5, y: self.view.bounds.height - 175)
then, in some other function, I place the node containing the SCNCamera to my desired position and angle.
// (exemplary)
cameraNode.position = initialStartPosition + SCNVector3(x: -0.5, y: 0.5, z: -(Float(shiftCurrentDistance * 2.0 - 2.0)))
cameraNode.eulerAngles = SCNVector3(-15.0.degreesToRadians, -15.0.degreesToRadians, 0.0)
The result, is a kind of window (the new SCNView) at the bottom of the screen, displaying the same SceneKit content as in the main sceneView, viewed trough the perspective of the SCNCamera plus its node position, and that very nicely.
In a common iOS/Swift/ARKit project, this construct generates some side effects, that one may struggle into.
1) Mainly, the new SCNView shows SceneKit content from the desired perspective, but the background is always the actual physical camera feed. I could not figure out, how to make the background a static color, by still displaying all the SceneKit content. Changing the new scene's background property affects also the whole main scene, what is actually NOT desired.
2) It might sound confusing, but as soon as the following code get's included (which is essential to make it work):
overlayView.scene = scene
the animation speed of the entire scenes (both) DOUBLES! (Why?)
I got this corrected by adding/changing the following property, which restores the animation speed behavour almost like normal (default):
// add or change this in the scene setup
scene.physicsWorld.speed = 0.5
3) If there are actions like SCNAction.playAudio in the project, all the effects will no longer play - as long as I don't do this:
overlayView.scene = nil
Of course, the additional SCNView stops working but everything else gets gets back to its normal.
Use this code (as a starting point) to find out how to setup a virtual camera.
Just create a default ARKit project in Xcode and copy-paste my code:
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
#IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
sceneView.showsStatistics = true
let scene = SCNScene(named: "art.scnassets/ship.scn")!
sceneView.scene = scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 0, 1)
cameraNode.camera?.focalLength = 70
cameraNode.camera?.categoryBitMask = 1
scene.rootNode.addChildNode(cameraNode)
sceneView.pointOfView = cameraNode
sceneView.allowsCameraControl = true
sceneView.backgroundColor = UIColor.darkGray
let plane = SCNNode(geometry: SCNPlane(width: 0.8, height: 0.45))
plane.position = SCNVector3(0, 0, -1.5)
// ASSIGN A VIDEO STREAM FROM SCENEKIT-RECORDER TO YOUR MATERIAL
plane.geometry?.materials.first?.diffuse.contents = capturedVideoFromSceneKitRecorder
scene.rootNode.addChildNode(plane)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
sceneView.session.run(configuration)
}
}
UPDATED:
Here's a SceneKit Recorder App that you can tailor to your needs (you don't need to write a video to disk, just use a CVPixelBuffer stream and assign it as a texture for a diffuse material).
Hope this helps.
I'm a little late to the party, but I've had a similar issue recently.
As far as I can tell, you cannot directly connect a camera to a node's material. You can, however, use a scene's layer as a texture for a node.
The code below is not verified, but should be more or less ok:
class MyViewController: UIViewController {
override func loadView() {
let projectedScene = createProjectedScene()
let receivingScene = createReceivingScene()
let projectionPlane = receivingScene.scene?.rootNode.childNode(withName: "ProjectionPlane", recursively: true)!
// Here's the important part:
// You can't directly connect a camera to a material's diffuse texture.
// But you can connect a scene's layer as a texture.
projectionPlane.geometry?.firstMaterial?.diffuse.contents = projectedScene.layer
projectedScene.layer.contentsScale = 1
// Note how we only need to connect the receiving view to the controller.
// The projected view is not directly connected as a subview,
// but updates in projectedScene will still be reflected in receivingScene.
self.view = receivingScene
}
func createProjectedScene() -> SCNView {
let view = SCNView()
// ... set up scene ...
return view
}
func createReceivingScene() -> SCNView {
let view = SCNView()
// ... set up scene ...
let projectionPlane = SCNNode(geometry: SCNPlane(width: 2, height: 2)
projectionPlane.name = "ProjectionPlane"
view.scene.rootNode.addChildNode(projectionPlane)
return view
}
}

Object hovers above ground surface in ARKit

I placed an object on a plane but its displayed about 10-15 c.m. above said plane.
What code should I have to use for placing on the plane?
Here is the code
let scene = SCNScene(named: "art.scnassets/cup.scn")!
// Set the scene to the view
sceneView.scene = scene
Here is current scenario screenshot
object is not touching to plane
Your question isn't clear but please check this Code, it may help you
// helicopter is object of SCNNode
let dragonScene = SCNScene(named: "media.scnassets/helicopter.dae")!
for childNode in dragonScene.rootNode.childNodes {
// Adding all the child nodes
helicopter.addChildNode(childNode)
}
helicopter.scale = SCNVector3(x: 0.002, y: 0.002, z: 0.002)
helicopter.position = SCNVector3(x:2.0 , y:0.0, z:-1.6)
sceneView.scene.rootNode.addChildNode(helicopter)

Collada file not show clear in scenekit

i have imported my collada file of a 3d tunnel in xcode.
2.when i run my sample project it shows 3d tunnel very far and small
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
let scene = SCNScene(named: "art.scnassets/tube.dae")!
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = SCNLightTypeOmni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
i just import my collada 3d object in xcode default ship code what should i do to see my 3d tunnel near to the scene as in following pics
in Blender:
when i run SceneKit project the tunnel in simulator looks like this:
[2
5.How can i get my 3d tunnel same view in scenekit as it is in blender.
Does your Collada file already have a camera in it, i.e. the one you're using to produce the first rendering?
If so, locate that camera/node (childNodeWithName). Assign it as your view's point of view. Don't create a new camera in your code.
Or, if you really need to create a new camera, put it at the same location as the point of view used for the first rendering.

Swift, Symbol not found: _OBJC_CLASS_$_SCNParticleSystem

I've just picking up Swift and playing around with SceneKit. I created a simple "Smoke.scnp" particle emitter in the xcode 6 editor, and I'm trying to load that using the SCNParticleSystem class. I have included the SceneKit frame work with the project. When running without the particle system, I could load the camera and other nodes just fine, but including the particle system failed during linking time. The error I got was symbol not found for SCNParticleSystem.
I don't have much experience with SceneKit, so this may not be something specific to xcode6 or Swift. Just wondering if I have to set up anything else?
Below is the snippet of my code:
import SceneKit
import QuartzCore
class GameViewController: NSViewController {
#IBOutlet var gameView: GameView
override func awakeFromNib(){
// create a new scene
let scene = SCNScene()
// Add camera to scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// Place camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 2)
// Add particle emitter
let bgSmokeNode = SCNNode()
var particleSystem = SCNParticleSystem(named: "Smoke", inDirectory: "")
bgSmokeNode.addParticleSystem(particleSystem)
scene.rootNode.addChildNode(bgSmokeNode)
}
}