High CPU usage SceneKit - swift

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.

Related

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

Convert STRING name of texture to TEXTURE reference in blueprints

I have "Make Brush from Texture" node and I am able to select textures from the dropdown list in editor, but how I can set this texture field realtime from blueprint if its name is saved in the string variable (for example, "af"). Thanks.
Example:
https://blueprintue.com/blueprint/4indn35l/
SUGGESTION:
In theory, there should be an ARRAY with all loaded system textures that I can loop, compare the name of the texture one-by-one, and post the right texture to in-socket of "Make Brush from Texture".
Thanks!
You can do this by using soft object paths.
Create a Make Soft Object Path node
The Path String should be the full path of a valid texture. You can see what that should look like by right-clicking one of your textures in the asset browser, selecting 'Copy Reference', and pasting that into a text field.
Plug the result of the 'Make Soft Object Path' node into a 'Convert to Soft Object Reference` node.
Plug the result of that into a 'Resolve Soft Reference' node.
Plug the result of that into a 'Load Asset Blocking' or 'Async Load Asset' node.
Plug the result of that into a Cast To Texture2D node.
Plug the result of a successful cast into your Make Brush From Texture node.
I wasted 2 hours to discover that there's a node called "Import File as Texture 2D"
I tried to make #Rotem answer work but every casting fails, it would be useful to me because i'm trying to make the same with .wav files, and unfortunately there's no function for them

How to remove all nodes with a specific name spritekit

Is there a simple way to remove all spritekit nodes with a given name. I am making a project that has waves of spaceships and at a certain score, I want the old spaceships to dissappear so that new, harder spaceships replace them. I gave all of the level one spaceships the same name and was wondering if there was a way that I could remove them all based on this factor.
It depends on how you created the spaceships. If you made them all with the same name maybe you can try:
self.enumerateChildNodesWithName("spaceShip"){
spaceShip.removeFromParent()
}
//or try
for child in self.children{
if child.name == "spaceShip"{
child.removeFromParent
}
}

Load .obj to .scn with multiple sub-objects, textures, materials in SceneKit & Model I/O?

I'm currently working with large .obj files in Apple's SceneKit/Model I/O that contain multiple objects within, each with separate textures and materials. This means I cannot apply one single texture to the file like many other form posts suggest. Is there a good way to import in the the materials and textures?
I have my obj mtl and jpg's all in one directory where I'm also putting the scn scene.
The code currently follows this design, where I access it from its respective location, load it into a MDLAsset then place it into a SCNScene where it is saved back to a file to be loaded in later in the code.
//...
// Get the mesh from the obj object
let asset = MDLAsset(url:url)
//asset.loadTextures()
guard let object = asset.object(at: 0) as? MDLMesh else {
fatalError("Failed to get mesh from obj asset.")
}
// Wrap the ModelIO object in a SceneKit object
let scene = SCNScene()
let node = SCNNode(mdlObject: object)
scene.rootNode.addChildNode(node)
// Get the document directory name the write a url for the object replacing its extention with scn
let dirPaths = FileManager().urls(for: .documentDirectory, in: .userDomainMask)
let fileUrl = dirPaths[0].appendingPathComponent(url.lastPathComponent.replacingOccurrences(of: ".obj", with: ".scn"))
// Write the scene to the new url
if !scene.write(to: fileUrl, delegate: nil) {
print("Failed to write scn scene to file!")
return nil
}
// ...
The MDLAsset.loadTextures function has no documentation and only causes a memory leak so at the time of this post, it's not an option. Opening the model by hand and hitting the convert to SCNScene dosn't work either as I still lose the materials. Additionally I want this to be automated in code to allow for downloading and conversion of models at runtime.
It seems like there is not built in way to do this except to do each texture and material by hand in the code, which is easy when it's only one complete texture but this model might have 100 different materials. It looks like it requires me to parse the obj/mtl manually and then create and assign the materials by hand. This seems completely unreasonable and I figure there must be a better way that I don't know about.
When you import an OBJ file via Model I/O as an MDLAsset, it will arrive as a collection of one or more MDLMeshes. The meshes will have MDLMaterials associated with them, and the MDLMaterial will have attributes. Those attributes will be numeric, file paths, or images. You need to iterate the properties, and check if there is a path.
https://developer.apple.com/documentation/modelio/mdlmaterialproperty
If there is, it will likely be a fileURL with the same content as was in the OBJ file's associated MTL file.
The properties described in the MDLScatteringFunction correspond to the various properties in a typical MTL file.
https://developer.apple.com/documentation/modelio/mdlscatteringfunction
MDLAsset.loadTextures will add an MDLTextureSampler value to the property, if Model IO can actually find the texture referenced in the MTL file.

Get reference to a Particle System created in Scene Editor

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.