Search code examples
iosscenekitparticles

How to rotate a particle with a specific angle programmatically in SceneKit?


I would like to rotate a particle, it is a simple line, emitted once in the center of the screen.

After I touch the screen, the method is called, and the rotation changes all the time. With 10° or 180°, around the x or z axis, the result is the same: the angle is N°, then Y°, then Z° (always a different number, with a random difference between one another : with 10°, it is not offset by 10 each time, but by a random number). Would you know why?

func addParticleSceneKit(str:String){
    var fire = SCNParticleSystem(named: str, inDirectory: "art.scnassets/Particles")
    fire.orientationMode = .Free
    fire.particleAngle = 90
    //fire.propertyControllers = [ SCNParticlePropertyRotationAxis : [1,0,0] ] // should it be a SCNParticlePropertyController? I don't know how to use it then. But it would not be for an animation in my case.
    emitter.addParticleSystem(fire)

Thanks


Solution

  • The particleAngleVariation property controls the random variation in initial particle angles. Normally that defaults to zero, meaning particle angle isn't randomized, but you're loading a particle system from a file, so you're getting whatever is in that file — setting it to zero should stop the randomization you're seeing. (You can also do that to the particle system in the file you're loading it from by editing that file in Xcode.)


    By the way, you're not adding another new particle system to the scene every time you want to emit a single particle, are you? Sooner or later that'll cause problems. Instead, keep a single particle system, and make emit more particles when you click.

    Presumably you've already set its emissionDuration, birthRate, and loops properties in the Xcode Particle System Editor so that it emits a single particle when you add it to the scene? Then just call its reset method, and it'll start over, without you needing to add another one to the scene.


    Also, regarding your comment...

    fire.propertyControllers = [ SCNParticlePropertyRotationAxis : [1,0,0] ] 
    

    should it be a SCNParticlePropertyController? I don't know how to use it then. But it would not be for an animation in my case.

    Reading the documentation might help with that. But here's the gist of it: propertyControllers should be a dictionary of [String: SCNParticlePropertyController]. I know, it says [NSObject : AnyObject], but that's because this API is imported from ObjC, which doesn't have typed collections. That's why documentation is important — it says "Each key in this dictionary is one of the constants listed in Particle Property Keys, and the value for each key is a SCNParticlePropertyController object..." which is just long-winded English for the same thing.

    So, passing a dictionary where the key is a string and the value is an array of integers isn't going to help you.

    The docs also say that property controllers are for animating properties, and that you create one from a Core Animation animation. So you'd use a property controller for angle if you wanted each particle to rotate over time:

    let angleAnimation = CABasicAnimation()
    angleAnimation.fromValue = 0 // degrees
    angleAnimation.toValue = 90 // degrees
    angleAnimation.duration = 1 // sec
    let angleController = SCNParticlePropertyController(animation: angleAnimation)
    fire.propertyControllers = [ SCNParticlePropertyAngle: angleController ]
    

    Or for rotation axis if you wanted particles (that were already spinning freely due to orientation mode and angular velocity) to smoothly transition from one axis of rotation to another:

    let axisAnimation = CABasicAnimation()
    axisAnimation.fromValue = NSValue(SCNVector3: SCNVector3(x: 0, y: 0, z: 1))
    axisAnimation.toValue =NSValue(SCNVector3: SCNVector3(x: 0, y: 1, z: 0))
    axisAnimation.duration = 1 // sec
    let axisController = SCNParticlePropertyController(animation: axisAnimation)
    fire.propertyControllers = [ SCNParticlePropertyRotationAxis: axisController ]