SceneKit Particle Systems in a Swift Playground - swift

Usually I instantiate SCNParticleSystems by using the file initializer like this:
var stars = SCNParticleSystem(named: "Stars.sncp", inDirectory: nil)
However this project requires a Swift Playground and when I try to use that init function with systems stored in the playground's Resources folder it returns nil (even if I change the specified directory to "Resources" or "/Resources" etc. etc. ).
Are Playground resource paths handled differently to normal apps or am I making a really stupid filenaming mistake?

In Xcode 11 and later there's no preconfigured .scnp Particle System file. Instead you can use Particle System object coming directly from Xcode library.
Or, as always, you can create a particle system in Xcode Playground programmatically.
About Swift Playgrounds for iPad read here.
Here's a code:
import PlaygroundSupport
import SceneKit
let rectangle = CGRect(x: 0, y: 0, width: 1000, height: 200)
var sceneView = SCNView(frame: rectangle)
var scene = SCNScene()
sceneView.scene = scene
sceneView.backgroundColor = .black
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position.z = 70
sceneView.scene!.rootNode.addChildNode(cameraNode)
let particleSystem = SCNParticleSystem()
particleSystem.birthRate = 500
particleSystem.particleLifeSpan = 0.5
particleSystem.particleColor = .systemIndigo
particleSystem.speedFactor = 7
particleSystem.emittingDirection = SCNVector3(1,1,1)
particleSystem.emitterShape = .some(SCNSphere(radius: 15))
let particlesNode = SCNNode()
particlesNode.scale = SCNVector3(2,2,2)
particlesNode.addParticleSystem(particleSystem)
sceneView.scene!.rootNode.addChildNode(particlesNode)
PlaygroundPage.current.liveView = sceneView

I think you're doing a mistake in filename extension. It is .scnp and not .sncp.
Either try without any extension -
var stars = SCNParticleSystem(named: "Stars", inDirectory: nil)
or try with correct extension -
var stars = SCNParticleSystem(named: "Stars.scnp", inDirectory: nil)

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.

Is there a good way to implement RealityKit into a Swift playground?

I want to be able to add a file created in Reality Composer into a Swift Playground for testing purposes. I also think it would be interesting to use as a supplement to a playbook. How would one go about adding an rcproject into a playground like the Experience.rcproject Apple provides with the scene “Box”?
You can read a .reality file format in Playground using the following code:
import Cocoa
import RealityKit
import PlaygroundSupport
let arView = ARView(frame: CGRect(x: 0, y: 0, width: 800, height: 200))
arView.environment.background = .color(.black)
let fileURL = Bundle.main.url(forResource: "main", withExtension: "reality")
let bowlingScene = try! Entity.load(contentsOf: fileURL!)
let anchor = AnchorEntity()
anchor.addChild(bowlingScene)
anchor.scale = [4,4,4]
anchor.position.y = -0.5
arView.scene.anchors.append(anchor)
PlaygroundPage.current.liveView = arView
So, supply a .playground file with a main.reality scene, making it nested.
About Swift Playgrounds for iPad read here.

AR with iOS: putting a light in the scene makes everything black?

Ok, I am trying desperately to achieve this sort of warm lighting on my objects when added to my ARScene in Swift/Xcode - warm lighting and little glowing lights around:
To be clear, I do NOT want the objects I add to my scene to look like they belong in the surrounding room. I want them to stand out/ look warm and glow.All the tutorials on ARKit teach you how to mimic the lighting of the actual room.
Xcode has several lighting options, pulling from the surroundings gathered by the camera because with:
if let lightEstimate = session.currentFrame?.lightEstimate
I can print out the warmth, intensity, etc. And I also have these properties currently set to match the light of room:
sceneView.automaticallyUpdatesLighting = true
extension ARSCNView {
func setup() { //SCENE SETUP
antialiasingMode = .multisampling4X
autoenablesDefaultLighting = true
preferredFramesPerSecond = 60
contentScaleFactor = 1.3
if let camera = pointOfView?.camera {
camera.wantsHDR = true
camera.wantsExposureAdaptation = true
camera.exposureOffset = -1
camera.minimumExposure = -1
camera.maximumExposure = 3
}
}
}
I have tried upping the emission on my object's textures and everything but nothing achieves the effect. Adding a light just turns the objects black/no color.
What is wrong here?
To create this type of glowing red neon light result in ARKit. You can do the following.
You need to create a reactor.scnp (scenekit particle System File) and make the following changes to create the glowing red halo. This should be place in your Resources directory of the playground along with the file spark.png
These are the settings to change from the default reactor type. Leave all the other settings alone.
Change the Image animate color to red/orange/red/black
speed factor = 0.1
enable lighting checked
Emitter Shape = Sphere
Image Size = 0.5
Image Intensity = 0.1
Simulation Speed Factor = 0.1
Note: The code below is playground app I use for testing purposes. You just tap anywhere to add the Neon light into the scene. You can place as many neon lights as you like.
import ARKit
import SceneKit
import PlaygroundSupport
import SceneKit.ModelIO
class ViewController: NSObject {
var sceneView: ARSCNView
init(sceneView: ARSCNView) {
self.sceneView = sceneView
super.init()
self.setupWorldTracking()
self.sceneView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(ViewController.handleTap(_:))))
}
private func setupWorldTracking() {
if ARWorldTrackingConfiguration.isSupported {
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal
configuration.isLightEstimationEnabled = true
self.sceneView.session.run(configuration, options: [])
}
}
#objc func handleTap(_ gesture: UITapGestureRecognizer) {
let results = self.sceneView.hitTest(gesture.location(in: gesture.view), types: ARHitTestResult.ResultType.featurePoint)
guard let result: ARHitTestResult = results.first else {
return
}
let cylinder = SCNCylinder(radius: 0.05, height: 1)
cylinder.firstMaterial?.emission.contents = UIColor.red
cylinder.firstMaterial?.emission.intensity = 1
let spotLight = SCNNode()
spotLight.light = SCNLight()
spotLight.scale = SCNVector3(1,1,1)
spotLight.light?.intensity = 1000
spotLight.castsShadow = true
spotLight.position = SCNVector3Zero
spotLight.light?.type = SCNLight.LightType.directional
spotLight.light?.color = UIColor.white
let particleSystem = SCNParticleSystem(named: "reactor", inDirectory: nil)
let systemNode = SCNNode()
systemNode.addParticleSystem(particleSystem!)
let node = SCNNode(geometry: cylinder)
let position = SCNVector3Make(result.worldTransform.columns.3.x, result.worldTransform.columns.3.y, result.worldTransform.columns.3.z)
systemNode.position = position
node.position = position
self.sceneView.scene.rootNode.addChildNode(spotLight)
self.sceneView.scene.rootNode.addChildNode(node)
self.sceneView.scene.rootNode.addChildNode(systemNode)
}
}
let sceneView = ARSCNView()
let viewController = ViewController(sceneView: sceneView)
sceneView.autoenablesDefaultLighting = false
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = viewController.sceneView
If your looking for a neon/glowing effect in your scene... these previous answers to a similar question asked about glowing/neon lighting should give you some guidance.
As you will see from the answers provided sceneKit does not have built-in support for volumetric lighting, all the approaches are more hacks to achieve a similar effect to a glowing light.
iOS SceneKit Neon Glow
To add a "red" directional light effect to your scene... which is an alternative to using sceneView.autoenablesDefaultLighting = true
let myLight = SCNNode()
myLight.light = SCNLight()
myLight.scale = SCNVector3(1,1,1)
myLight.intensity = 1000
myLight.position = SCNVector3Zero
myLight.light?.type = SCNLight.LightType.directional
myLight.light?.color = UIColor.red
// add the light to the scene
sceneView.scene.rootNode.addChildNode(myLight)
note: This effect makes all the objects in the scene more reddish.

Getting the SCNRenderer from an instantiated SCNScene

I would like to know how I am supposed to extract the SCNRenderer from an instantiated SceneKit scene. I am trying to get the AVAudioEngine which lies in the SCNRenderer so that I can apply audio filters to my nodes.
Here is the override didFinishLaunching part reduced to relevant code:
override func awakeFromNib() {
// create a new scene
let scene = SCNScene()
// 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)
// set the scene to the view
self.gameView!.scene = scene
gameView.delegate = self
}
Please if someone can give me a pointer, I would really appreciate it as I was able to run sounds in positional but now am stuck with using the AVEngine graph to do stuff like:
AVAudioInput > AVAudioUnitDistortion > AVAudioOutput and start doing some fun mixing.
Edit:
This is what I had in mind for the engine:
distortion = AVAudioUnitDistortion()
let URL = NSURL(fileURLWithPath: dataPath+"/welcome.aiff")
if(NSFileManager.defaultManager().fileExistsAtPath(dataPath+"/welcome.aiff")){
let source = SCNAudioSource(URL: URL)!
source.volume = 30.0
source.reverbBlend = 50.0
source.rate = 0.9
let clip = SCNAudioPlayer(source: source)
engine = clip.audioNode!.engine
distortion.loadFactoryPreset(AVAudioUnitDistortionPreset.SpeechRadioTower)
engine.attachNode(distortion)
engine.connect(clip.audioNode!, to: distortion, format: nil)
engine.connect(self.distortion, to: engine.outputNode, format: nil)
return clip
But I am now having a null pointer exception over the distortion AVAudioUnitDistortion instance.
Where am I going wrong ?
audioEngine is a property on the SCNSceneRenderer protocol, not on the SCNRenderer class. Since SCNScene conforms to SCNSceneRenderer, scene.audioEngine will work.
edit:
Since SCNView conforms to SCNSceneRenderer, gameView.audioEngine will work.

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)
}
}