Get reference to a Particle System created in Scene Editor - swift

I've created a particle system in the Scene Editor (not the particle Editor), and it's named "particles" (this is the default name).
Back in the ViewController, I'm attempting to get a reference to this particle system and change some properties of it.
But I can't figure out why this doesn't work:
let particleSystem = SCNParticleSystem(named: "particles", inDirectory: "")
particleSystem?.isAffectedByGravity = true
I know it's possible to set gravity on within the Scene Editor, but I'm simply using this as a test to see if the reference to the Particle System is working. It's not.
What am I missing or doing wrong?
ADDITIONAL EFFORTS:
As per Rickster's suggestion, trying this:
let particleSystems = scene.particleSystems
let myParticleSystem = particleSystems?[0]
myParticleSystem?.isAffectedByGravity = true
print(particleSystems)
This has now this problem:
My thinking (faulty as it is) was that the array's 0 location would have the only particle system I have in this scene.

Here is a picture of the scene I got:
And the code to get a reference to the particle system assigned to the particles SCNNode instance:
In Swift 2.3 (and earlier)
let particlesNode:SCNNode = scene.rootNode.childNodeWithName("particles", recursively: true)!
let particleSystem:SCNParticleSystem = (particlesNode.particleSystems?.first)!
In Swift 3.0
let particlesNode:SCNNode = scene.rootNode.childNode(withName: "particles", recursively: true)!
let particleSystem:SCNParticleSystem = (particlesNode.particleSystems?.first)!
Alternative Array Index Syntax
let particleSystem:SCNParticleSystem = (particlesNode.particleSystems?[0])!

As the docs for that initializer suggest, it's for loading particle systems that are in their own files. When you make a particle system in the scene editor, the particle system isn't its own file — it's part of the scene file you're making.
To find it... well, first you have to load the SCNScene object that file represents. Assuming you've done that already, you'll need one of two ways to access the particle system depending on how you defined it in the editor:
If the particle system is attached to a node — the straightforward way to position it in the scene — you can use the scene's rootNode property to get at the scene contents, then use methods like this one to find the node containing the particle system using the name you assigned it in the editor. (You did give it a name in the editor, right? If not, go do that... or worst case, there's always this method that lets you search for nodes based on any test you can code.)
If not, use the scene's particleSystems property to find all systems that aren't attached to nodes.

Related

Xcode 11 Action Editor? How to invoke?

In an earlier post I asked about the right settings for initializing a SCNParticleEngine. With some hints from Zay
I managed to get both the SCNP and code paths to work – almost identically. Except that the SCNP path had the particles shrinking over time while the coding path did not.
Turns out that buried inside the SCNP file is a dictionary entry “Size” which is a SCNParticlePropertyController. Here’s the code from the debugger:
The gotcha is that when I open the SCNP file in the editor, I can see the stars being animated etc. But there is no sign of an Action or any affordance to open such an editor. Now, this may be related to the fact that the SCNP file is all about the PE parameters for initializing the PE. There is NOT an actual node or object on the stage. One thought is you need to have an editable action or Xcode won’t open the Action editor?
How this action got there or how I might edit it is not clear. I assume someone put it in the SCNP in an earlier version of Xcode. Then the Action was forgotten. Or something like that. It may simply be a bug/corrupted SCNP file.
As you mention this is Core Animation animation driving SCNParticlePropertySize. It is possible to configure it in the SceneKit scene editor via the Attributes inspector (not the Action editor).
To be able to control the size of your particles over time (i.ex. let them shrink) using code only, you have to use a SCNPropertyController and a CAKeyframeAnimation like so:
// ANIMATE PARTICLE SIZE OVER TIME
let animationSize = CAKeyframeAnimation()
animationSize.values = [0.7, 0.3, 0.2, 0.0]
animationSize.keyTimes = [0.0, 0.125, 0.3, 1.0]
animationSize.duration = CFTimeInterval(10.0)
animationSize.calculationMode = .cubic
animationSize.timingFunction = CAMediaTimingFunction.init(name: .linear)
and then you do:
// SET PROPERTY CONTROLLER
let sizeController = SCNParticlePropertyController(animation: animationSize)
myParticleSystem.propertyControllers = [SCNParticleSystem.ParticleProperty.size: sizeController]
with the same approach you can animate the particle colours in a changing sequence over time.

SWIFT: Load different reality Scenes via Variable

So, i am trying to load different Scenes made in RealityComposer, depending on a variable.
What worked so far:
let SceneAnchor = try! Experience1.loadScene()
arView.scene.anchors.append(SceneAnchor)
return arView
Now i looked into apples Documentation and saw the possibility of:
if let anchor = try? Entity.loadAnchor(named: "Scene") {
arView.scene.addAnchor(anchor)
}
where i thought i could just change "Scene" to "Scene(myVar)"
But once i have more than one scene in my file the first solution doesnt work anymore
and the second one doesnt work as well.
What am i missing?
I also looked into working with filenames and was able to make an array of all my .reality Files and Store them in an Array, so i thought i could recall that via the index, but
arrayName[1].loadScene() doesnt seem to work either, eventhough i can print the filenames to console.
Thanks in advance :)
The fact is that Reality Composer creates a separate static method for each scene load. The name of such method is load+scene name. So, if you have 2 scenes in your Exprerience.xcproject with the names Scene and Scene1, then you have 2 static methods
let scene = Experience.loadScene()
let scene1 = Experience.loadScene1()
Unfortunately it is not possible to use the scene name as a parameter, so, you need to use the switch statement in your app to select the appropriate method.

What is the difference between loading a scene w/ .instance and using .new?

Working in Godot 3.2, I have a scene, Player.tscn. Near the top of Player.tscn, I have "class_name Player"
Now, when instantiating the Player, I have, as far as I see it, two options:
player = Player.new()
or
player = load("res://Player.tscn").instance() as Player
Now, the first version seems best to me...but it clearly isn't. If I use .new(), it claims that it has no children, and any method calls that attempt to get to its children (.get_texture() on a Sprite, e.g.), produces things like "Attempt to call function 'get_texture' in base 'null instance' on a null instance", because apparently Player has a no children.
Of course, doing it the second way, everything works fine. But why? Why can't I just use .new() if I've registered it as a class using class_name?
I'm new to Godot myself so I may be wrong, but I think it's because the .new() keyword is a language feature for loading single classes/nodes,
whereas .instance() is more of an engine feature that takes a packaged scene which is a combination of many classes/nodes, and restores their relative position in the tree, attaches scripts and resources, etc
So if your player is a tscn scene, you'd do something like:
var player = load("res://etc/Player.tscn").instance()
get_tree().root.add_child(player)
player.global_transform = etc ...
which would load the root node of Player.tscn and all of its children (rigidbodies, collidershapes etc) into the scene tree, set their relative positions, connect the scripts and what have you
Or if your Player is a class or a GDScript, you could do
var player = Player.new()
get_tree().root.add_child(player)
player.global_transform = etc ...
Which would add the single-node root player object only into the scene tree, but not any children
Once it's in the scene tree and _ready()'s triggered though, you can instantiate and attach the child component nodes (.new() > parent.add_child() > set transform) from the script itself

High CPU usage SceneKit

When I display a node from a COLLADA file, the CPU usage goes up to 100%+.
Link to pic
I am not using the simulator, I am using my actual phone. The model consists of about 80k vertices.
Here is how I load the model:
// Add Character
func addModel(name:String)
{
// Load COLLADA Model
if let myScene = SCNScene(named: "Assets.scnassets/"+name+"/"+name+".dae")
{
// Recurse through all the child nodes in the model and add to modelNode
for node in myScene.rootNode.childNodes as [SCNNode]
{
modelNode.addChildNode(node)
}
// Add modelNode to scene
self.rootNode.addChildNode(modelNode)
}
else
{
print("Error loading model: "+name)
}
}
The model is 122MB and can be found here:
Link to zip
I have tried with different models, but CPU goes nuts on each one of them. They are all about 122MB (which sounds large) and about 80k vertices.
Update
Tried to lower the poly count in SceneKit. In blender i lowered vertices from 20k to 5k (see here). But when I load the model in SceneKit the poly count is still the same (68k). I also tried converting the model and the animations to .SCN instead. This resulted in much lower file size, however the same poly count and CPU usage.
Pictures of showStatistics
I think what I need to do is lower the poly count, not so much the file size (don't think that would harm though).
Update 2
Now I actually managed to lower the poly count to about 48k. Still lagging. If I remove the texture, the CPU is much lower.
I'm not positive this is your usage issue but your method of loading your file could be streamlined.
You don't need to add each individual node from your DAE file to your modelNode. Ideally, your Collada model will have its own root node with a unique name. Just add that node to modelNode and all its children will be included. In the sample below, lyso_ribbons is the name of the root node in the DAE as well as the name of the DAE file itself.
guard let lysoRibbons = SCNScene(named: "lyso_ribbons")
else { print("Couldn't find molecule in dictionary (lysoRibbons)")
return }
let modelNode = lysoRibbons.rootNode.childNodeWithName("lyso_ribbons", recursively: false)!
All the child nodes that tagged along with the root are still accessible by name via childNodeWithName. You can inspect your DAE's scene graph in the editor window, just click on it in the navigator panel and, within the editor, click on the small square far lower left of the editor window. If, for some reason, your DAE model is lacking a root node you can create one here and move all other nodes into it. Also see: Transform and Rotate in Scenekit
You can create nodes in the left-hand panel of the editor window displaying your DAE file. Click on the + sign, far lower left of the panel. Drag that <untitled> object to the top, just under "Scene graph". Then group-select all other nodes and drag them into this new object. Give the new object a unique name. You'll notice the new node has a grayed icon meaning it has no geometry of its own.
A better way to do this is to plan ahead when creating your Collada model using nulls, with unique names, to organize your geometry nodes into meaningful sub-groups. The nulls will then be imported as parent nodes of those sub-groups. Place all nodes into one master null that will become the root for your model.

Trouble with SKEmitterNode - doesn't display colors created in sks file

I have the function below to show an emitter. It is supposed to pick which asks file to use for the emitter based upon the color passed into the function. The sks files have been created and named based upon their colors and they display the proper coloring in Xcode.
However when run on the simulator or the device, it does not appear that the coloring from the sks file is honored. No matter what color is passed in, the emitter shows the same particle colors. BTW this is a spark based emitter.
Any ideas what I may be doing wrong?
func showEmitter(theColor:String){
var ourEmitterName:String?
switch(theColor) {
case "black","white":
ourEmitterName = "blackwhiteemitter"
default:
ourEmitterName = "\(theColor)emitter"
}
let emitterPath = NSBundle.mainBundle().pathForResource(ourEmitterName, ofType: "sks")
let thisEmitter:SKEmitterNode = NSKeyedUnarchiver.unarchiveObjectWithFile(emitterPath!) as SKEmitterNode
thisEmitter.zPosition = SceneLevel.background.rawValue
self.addChild(thisEmitter)
}
Thanks for your help - Ken
I found the problem. In my emitters I had the Blend Mode set to 'Add'. After I changed it to 'Alpha' everything worked fine.
Not sure why that solved it, since I don't know to what was 'added' to what.