WIP: My approach to a (poor man's?) particle system for Fabric

13»

Comments

  • malbrechtmalbrecht Fabric for Houdini Posts: 752 ✭✭✭

    @craouette : I think you need to add a feature/property like "previous position" and - in my current way of thinking, which is open to arguments :smile: - have your solver take care of the swapping. Do you want me to write an example solver for that? I plan to release the next iteration today and could include that.

    @Helge : I appreciate your position, yet I agree on that of my clients, which is different ... "perfectly safe" and "cloud" are not compatible in our world view. I think we can become friends although we disagree on this ...


    Marc Albrecht - marc-albrecht.de - does things.

  • HelgeHelge Moderator, Fabric Employee Posts: 314 Fabric Employee

    Hey @craouette,

    hmmm good point. This seems like a missing preset. You can of course do it in KL. But I have filed FE-6254 to offer this in a future release. You'd do it like this ~ more or less: New function node:

    Ports:

    io geometry $TYPE$
    in attrAName String
    in attrBName String

    Code:

    dfgEntry {
      GeometryAttributes attributes = geometry.getAttributes();
      Vec3Attribute attrA = attributes.getAttribute(attrAName);
      if(!attrA) {
        report('Attribute '+attrAName+' does not exist or is not a Vec3.');
        return;
      }
      Vec3Attribute attrB = attributes.getAttribute(attrBName);
      if(!attrB) {
        report('Attribute '+attrBName+' does not exist or is not a Vec3.');
        return;
      }
    
      for(Size i = 0; i < attrA.values.size(); i++) {
        Vec3 temp = attrA.values[i];
        attrA.values[i] = attrB.values[i];
        attrB.values[i] = temp;
      }
      attrA.incrementVersion();
      attrB.incrementVersion();
    }
    

    I haven't tried this - so I hope it is free of errors :smile: I hope this helps!

    Research Engineer @ Fabric Software

  • HelgeHelge Moderator, Fabric Employee Posts: 314 Fabric Employee
    edited March 2016

    Btw this is specific for Vec3, the preset we will provide in a future release will of course work for any type.

    I assume btw that your simulation node might just be a KL function which does all of this stuff.

    • Integrate forces into velocity
    • Integrate velocity into position
    • Swap positions
    • etc

    Research Engineer @ Fabric Software

  • craouettecraouette Posts: 113

    Yes Helge, you are right. I am on my way to do exactly this.

    But, concerning the node, I was more thinking on swapping the names... to avoid moving data. In C++ I would use 2 arrays and 2 pointers, and just swap the pointers at the end of each simulation step => no data is moved, no need for a temporary array but only the pointers should be used, never the arrays => quick and memory efficient. swapping the values one by one is not quick!!
    Of course, it is possible to create the 2 attributes, and 2 names, use the names to get the attributes, and swap the names at the end of a time step. but the problem is: the position attribute is handled differently, as it is the mandatory attribute, with a reference stored in GeometryAttributes, and used to communicate to and from the DCC. so, swapping name won't do the trick, because the DCC will miss the swap. I am correct? So, what am I looking for is a piece of code that swap the positions attribute.
    Is changing the names of the attributes (removing them, then adding them again to GeometryAttributes) and updating the Ref enough?

  • HelgeHelge Moderator, Fabric Employee Posts: 314 Fabric Employee
    edited March 2016

    In fact you should be able to do what I did above, and just swap the arrays. As in

    Vec3 tmp[] = attrA.values;
    attrA.values = attrB.values;
    attrB.values = tmp;
    

    Changing the names is not viable, since the GeometryAttributes structures manages a map of names also. Just changing the name of an attribute will mess things up afaik.

    Research Engineer @ Fabric Software

  • craouettecraouette Posts: 113

    yep, exactly what I was looking for... thanks a lot Helge.

    another question, the aim is to create the raw part of the particle system. Then, user will be able to add attributes, like temperature for example.
    But, the user will also have to provide how the temperature is initialized and how it will evolve with each simulation step.
    what is the best way, canvas friendly, to do this?

  • HelgeHelge Moderator, Fabric Employee Posts: 314 Fabric Employee

    You mean like callbacks? Well you'd design it the same way you would in other programming languages....

    The system could support something like ParticleUserData..... an interface. Your particle system data structure could then implement to store a list of these, which can be initiated upon adding them and then be invoked for simulation at every sim step... or whatnot.

    Note: This is a mockup - I haven't tested this:

    interface ParticleUserData {
      init(io GeometryAttributes attributes);
      simulate(io GeometryAttributes attributes);
    };
    
    object ParticleTemperature : ParticleUserData {
      ScalarAttribute attr;
      Scalar coolDownFactor;
    }
    
    function ParticleTemperature() {
      this.coolDownFactor = 0.9;
    }
    
    function ParticleTemperature(Scalar coolDownFactor) {
      this.coolDownFactor = coolDownFactor;
    }
    
    function ParticleTemperature.init(io GeometryAttributes attributes) {
      this.attr = attributes.getOrCreateScalarAttribute('temperature');
    }
    
    function ParticleTemperature.simulate(io GeometryAttributes attributes) {
      // for ex: cool down
      for(Size i=0;i<this.attr.values.size();i++)
        this.attr.values[i] *= this.coolDownFactor;
    }
    

    Research Engineer @ Fabric Software

  • craouettecraouette Posts: 113

    Yes, this what I did... but, it is not really canvas friendly, or Am I missing something? How the end user is able to provide an object respecting the interface just by using canvas nodes?

    You can use Attribute.Create to add the new data (as particles is a inheriting from Points). But, how the and user can create a subgraph that will be executed for each particle creation and each simulation step...

    The solution can be to use DFGBinding... but I am not sure it will be ok from the performance point of view?

  • HelgeHelge Moderator, Fabric Employee Posts: 314 Fabric Employee

    It should be fine in terms of performance - if the DFGBinding is per major operation and not per particle.

    Research Engineer @ Fabric Software

  • craouettecraouette Posts: 113

    that's mean I have to attach a Size bornedIndices[]; variable to my particles, so that initialization is launched once and work only on newly created particles. the perfomSimulationStep has already the activeIndices array to work on. correct?

  • HelgeHelge Moderator, Fabric Employee Posts: 314 Fabric Employee

    Well it depends on your implementation of course. But sort of - yes. Just don't invoke the DFGBinding per particle, but for a whole array of them.

    Research Engineer @ Fabric Software

  • malbrechtmalbrecht Fabric for Houdini Posts: 752 ✭✭✭

    Hi,

    just came back, trying to catch up here. Have to admit I am lost at some discussions.

    the perfomSimulationStep has already the activeIndices array to work on. correct?

    If that question relates to what I uploaded to GitHub: Yes, "active" is respected both for the simulation as for each particle. I have also added (for the next release) a check for gravity != 0.0, so that mass-free particles can be combined with particles with mass. I also plan to have an air resistance solver up and working soon.

    @craouette - could you please let us know if Helge's suggestion of swapping attributes works? If so, we could include that in the documetation for the particle system. If not, another idea might be to have a second position attribute ("alternateposition") and only use that on your specific solver, which would use this attribute and the original position every other frame. That way you would not have to copy data, yet the solver would still be able to use previous positions.

    Marc


    Marc Albrecht - marc-albrecht.de - does things.

  • craouettecraouette Posts: 113

    I will marc, but I am taking a different direction than the one you took...

    Particles are defined like this:
    object Particles : Points {
    GeometryAttributes globalAttributes;

      UInt32 activeIndices[], minActiveIndex, maxActiveIndex;  // min and max to minimize synchronisation with DCC only this set of particles is alive
      UInt32 diedIndices[];  // to tell the DCC which particles died during this step so that it can efficiently synchronise
      UInt32 lastNumActives;  // from lastNumActives to activeIndices.size() -1, the indices are newly created particles (during the current simulation step)
    };
    

    in this way, end user can add as many attribute as wanted, and they can be per particle or global.
    Then, I defined volumes, used to limit the forces actions to given volumes.
    interface VolumeQuery {
    Float32 inside( Vec3 pos );
    };

    object Volume: VolumeQuery {
    
    };
    
    object WholeSpaceVolume : Volume {
    };
    
    Float32 WholeSpaceVolume.inside( Vec3 pos )  {
      return 1.0;
    }
    
    
    object SphereVolume : Volume {
      Vec3 center;
      Float32 radius0, radius1;  // radius0 > radius1
    };
    
    Float32 SphereVolume.inside( Vec3 pos )  {
      Vec3 vdist = pos - this.center;
      Float32 dist = vdist.length();
      if(dist > this.radius0)  
        return 0.0;
      if(dist < this.radius1)
        return 1.0;
      return (this.radius0 - dist) / (this.radius0 - this.radius1);
    }
    

    after, I can define forces with first an interface, and a link to a volume:
    interface ForceQuery {
    Vec3 strengthAtPos( Vec3 pos );
    };

    object Force: ForceQuery {
      Volume volume;
    };
    
    object ConstantForce : Force {
      Vec3 force;
    };
    
    
    Vec3 ConstantForce.strengthAtPos( Vec3 pos )  {
      return this.force * this.volume.inside(pos);
    }
    
    
    object AttractorForce : Force {
      Vec3 center;
      Float32 strength, radiusPower; // radiusPower = 0 => constant, -1 => 1/r, -2 => 1/r2
    };
    
    
    Vec3 AttractorForce.strengthAtPos( Vec3 pos )  {
      Vec3 vdist = pos - this.center;
      Float32 dist = vdist.length();
      vdist.normalize();
      return vdist * pow(dist, this.radiusPower) * this.strength * this.volume.inside(pos);
    }
    

    The end user can create easily new forces... but will have to use kl code. And, while I am writting this, I think it would be much better define the Force interface by:
    add( Particles particles )
    this will use particles.getOrCreateVec3Attribute("force"), and add the force on all living particles.
    and this will allow end user to defines their on using DFGBinding.
    and this will also make possible to define interaction forces, as all particles are known when computing the force.

    and now, I am able to define simulation:

    object ParticleSimulation {
      Particles particles;
    
      Float32 firstCreationTime;  // in second
      Float32 lastCreationTime;  // in second
      Float32 minCreationRate, maxCreationRate;  // per second
      Emitter emitters[];
    
      Force forces[];
      DFGBinding customForces[];
    
      PolygonMesh colliders[];
    
      DFGBinding customInits[];
      DFGBinding customSimulationStep[];
    
      Float32 lastSimulationTime;
    };
    

    and a few more variables that will popup while doing the actual implementation.

    I am trying to respect Geometry philosophy... and being as flexible as possible.

  • malbrechtmalbrecht Fabric for Houdini Posts: 752 ✭✭✭

    Sorry, you lost me there - I don't see the advantage of your approach ... for example: Why should forces always be radial (i.e. only being defined by radii)? Gravity (as an example) is, for most practical cases, more of a linear force. So would be air resistance. Or: Why should active particles be inside a min-max range? If you have random forces killing off random particles, dead instances can be anywhere in the array, not just outside min-max.

    It's probably just a different way of thinking. Quite likely that both particle systems can benefit from an exchange of ideas, although I am a bit puzzled about discussing two approaches in one thread.

    Marc


    Marc Albrecht - marc-albrecht.de - does things.

  • craouettecraouette Posts: 113
    edited March 2016

    Why should forces always be radial (i.e. only being defined by radii)?

    force are not always radial, this just one example of forces. Here another one:

    object ConstantForce : Force {
      Vec3 force;
    };
    
    function ConstantForce () {
      this.volume = WholeSpaceVolume();
      this.force = Vec3(1.0, 0.0, 0.0);
    }
    
    function ConstantForce (Vec3 force) {
      this.volume = WholeSpaceVolume();
      this.force = force;
    }
    
    function ConstantForce (Vec3 force, Volume volume) {
      this.volume = volume;
      this.force = force;
    }
    
    Vec3 ConstantForce.strengthAtPos( Vec3 pos )  {
      return this.force * this.volume.inside(pos);
    }
    

    and another one:

    object TurbulizedForce : Force {
      Scalar time;
      Vec3 center, amplitude, timeFrequency, spaceFrequency;
    };
    
    function TurbulizedForce () {
      this.volume = WholeSpaceVolume();
      this.time = 0;
      this.center = Vec3(0.0, 0.0, 0.0);
      this.amplitude = Vec3(1.0, 1.0, 1.0);
      this.timeFrequency = Vec3(1.0, 1.0, 1.0);
      this.spaceFrequency = Vec3(0.1, 0.1, 0.1);
    }
    
    function TurbulizedForce (Vec3 center, Vec3 amplitude, Vec3 timeFrequency, Vec3 spaceFrequency) {
      this.volume = WholeSpaceVolume();
      this.time = 0;
      this.center = center;
      this.amplitude = amplitude;
      this.timeFrequency = timeFrequency;
      this.spaceFrequency = spaceFrequency;
    }
    
    function TurbulizedForce (Vec3 center, Vec3 amplitude, Vec3 timeFrequency, Vec3 spaceFrequency, Volume volume) {
      this.volume = volume;
      this.time = 0;
      this.center = center;
      this.amplitude = amplitude;
      this.timeFrequency = timeFrequency;
      this.spaceFrequency = spaceFrequency;
    }
    
    Vec3 TurbulizedForce.strengthAtPos( Vec3 pos )  {
      Vec3 c = this.center + this.time * this.timeFrequency;
      Vec3 v = c + this.spaceFrequency * pos;
      Vec3 res = Vec3();
      res.x = pos.x + this.amplitude.x * perlinNoise(v.x, v.z, v.y);
      res.y = pos.y + this.amplitude.y * perlinNoise(v.y, v.x, v.z);
      res.z = pos.z + this.amplitude.z * perlinNoise(v.z, v.y, v.x);
      return res;
    }
    

    you can define as many as you want.... and add them the the forces array of the simulation. and the volume link allows to control where the force is applied.

    Why should active particles be inside a min-max range?

    the min max range is used by the DCC... when synchronization is performed, only particles in this range have change during the simulation step, so only these ones need to be read by the DCC. when doing the synchronization, the activeIndexes must be read, and the data of the particles need to be copied from Fabric, in the range to minimize data transfer.

    although I am a bit puzzled about discussing two approaches in one thread.

    Do you prefer I open another one?

  • malbrechtmalbrecht Fabric for Houdini Posts: 752 ✭✭✭

    Do you prefer I open another one?

    Well, it may be just me, but the way I understood this (colaborated) effort was that we work together in a kind of one direction.
    Discussing two different approaches - to me - feels like making sense in the beginning phase of a discussion, but when an implementation has started, it confuses older people (I can tell, because I am confused).
    I would have welcomed input and critique on my proposal of how to start this thing - I still welcome everything, but right now I am clueless about whether or not I should even continue with my approach. I love discussion and exchange of ideas. But wasting energy - I don't like.

    So ... I guess it's up to you: I would love you to take part in a discussion on how to get the best (simple, fast) particle simulation for Fabric hammered out. I am open to dumping my stuff all together. I would just like to know what's going on.

    Marc


    Marc Albrecht - marc-albrecht.de - does things.

  • malbrechtmalbrecht Fabric for Houdini Posts: 752 ✭✭✭

    Took me a bit longer than planned, but I figured out a plugin-system.

    Plugins are stackable by just pushing them into the plugins array, no matter which type of plugin.
    This one creates an infinite collider plane (free rotation/position). Velocity should get correctly fractioned (i.e. bouncing should correctly get damped). Right now it's reflecting 100%, but an additional dampening factor would be easy to implement. Standard use would probably be a ground plane.

    I fixed a few stupidities as well ... next upload to GitHub should have at least the plane collider and the cage system. Hopefully.
    So far I have not created presets, because the whole system is in flux, obviously.

    Marc


    Marc Albrecht - marc-albrecht.de - does things.

  • EricTEricT Administrator, Moderator, Fabric Employee Posts: 304 admin

    Great progress @malbrecht! Keep up the awesome work.

    Eric Thivierge
    Kraken Developer
    Kraken Rigging Framework

  • malbrechtmalbrecht Fabric for Houdini Posts: 752 ✭✭✭

    Thanks, Eric.

    Although my "stacking of collision plugins" technically works, I am having problems getting the collisions resolved. ONE is no problem, but the connection line of two planes is giving me headaches.

    I think I am going to add a function that allows a solver or plugin to get the originating position of a particle (through its emitter, so I only have to store a single index, not a full Vec3). That way infinite colliders could simply check for "this side / that side" instead of having to deal with ever changing positions.

    This is real fun. Real problems that MUST have solutions, if only I was intelligent enough to find them. I love it :)

    Marc


    Marc Albrecht - marc-albrecht.de - does things.

  • Roy NieterauRoy Nieterau Posts: 258 ✭✭✭

    Nice progress @malbrecht

    Although my "stacking of collision plugins" technically works, I am having problems getting the collisions resolved. ONE is no problem, but the connection line of two planes is giving me headaches.

    Is the problem here that one collision is computed after the other? I have no idea how multiple collisions that trigger at the same time (or are computed "as if" were run in parallel) are usually computed. All I can think of is using an iterative process with substeps.

    I think I am going to add a function that allows a solver or plugin to get the originating position of a particle (through its emitter, so I only have to store a single index, not a full Vec3). That way infinite colliders could simply check for "this side / that side" instead of having to deal with ever changing positions.

    Not entirely sure what you mean by this. What is the "originating position"?

  • malbrechtmalbrecht Fabric for Houdini Posts: 752 ✭✭✭

    Moin,

    sorry, I was just opening a leak in my brainstorming here ... trying to get a new GitHub upload done before settling for accounting today :-D

    My current terminology is this:

    • simulation: Holds all the global data for a simulation, mainly the point cloud (which can get its points from several emitters, see there), there can be more than one simulation per "scene", obviously, but each "addon" (Solver, Emitter, Plugin) is tied to "its" simulation
    • emitter: creates particles (more docs in the emitters.kl file to be uploaded)
    • solver: this is the function that gets called once per frame (todo: subframe handling?) and mainly deals with changing velocities and positions
    • plugin: these get called FROM the solver (so a basic solver might only call basic plugins while a specific, let's say, fluid solver might call only fluid plugins) and deal with points' attributes (age, killing them, adjusting positions etc)

    So inside a single plugin usually ONE collision gets checked, because (example) an "infinite plane" is ONE plugin (creating ONE collision entity).
    Right now I am recursively calling all previous plugins once a plugin has changed something to allow for adjustments, with the calculated velocities being added one to another. That does work to some degree, but it feels gritty.
    I am trying to provide some code documentation in the upcoming load (erm ... coming upload?) so - if you want to and have fun in sharing other people nightmares - you could have a look and brainstorm along.
    Not so much looking for "ready-made solutions", as this, for me, is an experiment to get a GRIP on the whole particle simulation thing and finding THE solution that works well enough to be extensible, understandable and usable. Lots of expectations there, I know :) And I have to comfort the "artists", showing pretty animations without any code alongside ... hard. I am so non-creative ...

    The "originating" position is this idea:

    An "emitter" creates particles at a specific position (which is the offset defined by the emitter PLUS the default origin position for particles as defined in the global simulation settings). For infinite planes (collision objects) and cages it would suffice to check the "sideness" of a given particle against the origin of that particle, because by definition the collision objects can not be penetrated.
    This idea obviously only works for infinite collision objects, as a limited plane (as Fabric understands the term) could get traveled around. Yet, for cages (i.e. areas in space that contain the particles) it would also make sense.

    I hope this helps - at least in following my thought process, if not in finding it convincing :-)

    Rewriting some stuff in particles.kl right now, will hopefully upload in a few minutes.

    Marc


    Marc Albrecht - marc-albrecht.de - does things.

  • Roy NieterauRoy Nieterau Posts: 258 ✭✭✭
    edited March 2016

    That bullet point list is a great one for the front page README.md on Github.

    So the plug-ins are just processed one after the other as an array of Plugins in the Simulation. Correct? That would make it possible to stack any set of Plugins as you'd like and combine them.

    An "emitter" creates particles at a specific position (which is the offset defined by the emitter PLUS the default origin position for particles as defined in the global simulation settings). For infinite planes (collision objects) and cages it would suffice to check the "sideness" of a given particle against the origin of that particle, because by definition the collision objects can not be penetrated.

    Sure. But this is still a Vec3 right? The collider should just compute the sidedness. Or are you just making assumptions that those collider planes are never tilted (like 45 degrees) and as such the sidedness is always dependent on a single axis?

    Rewriting some stuff in particles.kl right now, will hopefully upload in a few minutes.

    Whenever you feel the time is right then go for it. ;) By the way, those animated gifs are really helping to show off what this can do. With what software are you doing those screen captures?

  • malbrechtmalbrecht Fabric for Houdini Posts: 752 ✭✭✭

    So the plug-ins are just processed one after the other as an array of Plugins in the Simulation. Correct?

    actually in the solver ... at the moment :)
    The solver is what is getting called from the graph. The simulation (init) is called once, while the solver is getting called every frame.

    But this is still a Vec3 right? The collider should just compute the sidedness. Or are you just making assumptions that those collider planes are never tilted (like 45 degrees) and as such the sidedness is always dependent on a single axis?

    I am (now) defining planes by a point on the plane and a normal vector, so planes get be "anywhere in space, rotated any way". I am computing the sideness (sidedness?) by comparing the point's (particle's) position to the plane's normal, so this way I am saving some computations (which have to be done for each particle for each frame, so saving time is a good thing).

    With what software are you doing those screen captures?

    you are allowed to laugh:
    I didn't have the time to find a quick, well working, clean solution (based loosely on a quote by Schiller, obviously), so I used bb recorder (with which I am currently doing all my video tutorial screencaptures), export the capture, convert them (after cutting/editing) in VegasPro and combine them into a GIF with ImageMagick ... I guess I could go more convoluted - but this workflow is embedded into my muscle memory, I can edit the stuff to my liking and get exactly the result (including scaling etc) that I need.

    Marc


    Marc Albrecht - marc-albrecht.de - does things.

  • malbrechtmalbrecht Fabric for Houdini Posts: 752 ✭✭✭

    Well ... not really happy, but at least I got the issues with "fall-through" resolved, I guess. Unfortunately the "resting" positions of particles now are offset from the ground plane - I have a vague idea about why, but I need a break :)

    New files are going to get uploaded to GitHub now. Note that plugins.kl is a mess, as I swurbled around in there to get the issues resolved. Need to rewrite that from scratch, me guesses.

    Marc


    Marc Albrecht - marc-albrecht.de - does things.

  • Roy NieterauRoy Nieterau Posts: 258 ✭✭✭
    edited March 2016

    Nice progress!

    I wonder how cool it would look with randomized colors, or something based on velocity or age.

  • malbrechtmalbrecht Fabric for Houdini Posts: 752 ✭✭✭
    edited March 2016

    Thanks for the challenge - learned how to draw colored points to solve that riddle :)

    New plugin type: Heatspot. No cooling yet, just setting the temperature based on distance to the spot. Comes with a scalar-to-rgb-conversion function ...

    ... and, just to proof the concept: Plugin can be plugged in without thinking about plugin types. Just push it into the plugins array. I like that :)

    Need to take a nap now ... learned enough for today ...

    Marc


    Marc Albrecht - marc-albrecht.de - does things.

  • Roy NieterauRoy Nieterau Posts: 258 ✭✭✭

    And yes... that does look great! :smiley:

  • malbrechtmalbrecht Fabric for Houdini Posts: 752 ✭✭✭

    Thanks, @Roy Nieterau.

    I'd like to get some feedback on these thoughts (if this approach here is of interest outside the Marc-is-learning-something bubble):

    I think I now have some basic understanding about how this system should work. What I would like to do next is "kind of" rewrite it from scratch and do it right - defining reusable interfaces (for plugins, solvers etc) and minimizing memory consumption on a "min sim", i.e. not forcing features (Attributes) that are not needed.
    At the moment the interfaces are just the way I needed them while I was mocking up tests. That's the worst possible approach to development. A rework is therefor needed, if only to punish me for murking.

    I think a plugin should "require" a feature, so if, for example, a "heatspot" is plugged in, it should tell the simulation (init) that "temperature" is needed (besides, right now I am also adding a "color" Attribute, which is stupid, as I can calculate the point's color based on its temperature anyway).
    So plugging in any kind of "obstacle", "force", "restrain" or whatever will automatically introduce the needed attributes.

    Also I have been thinking about that "inactive points" thing. If you take the heatspot scene and remove the ground collision plane, the particles fall nicely into nothingness. The "cage" plugin I will put in next will allow the system to deactivate particles that are outside the "simulation space", so if you set the cage up to cover the camera-visible area, particles outside would get deactivated and would not take up calculation time.
    BUT ... the ideas about having additional arrays (like "active only" or "not active only") I somehow don't really like. If you are dealing with 20 Million particles, for example, you may have to arrays of several million entries each - that's going to get messy in terms of performance anyway.
    So I thought about using those classic double linked lists instead. A header struct will point to an arbitrary "first" point, from that (active) point you just iterate through the linked nodes (not using any indices at all), because the solver will always have to check all points anyway. If you create additional "groups" (by some octree filter for example), you just add a new double linked list, not messing with the original data at all.
    Through those lists you can browse forward or backward, getting all active particles until you reached "last node" in the list header. Deactivating a particle would then simply mean removing it from the list - done. It would never get called again, not even to check if it is active (thus saving CPU time). Reactivating it would simply mean plugging it anywhere into the chain, done.

    The basic structures of these lists/nodes (probably known to most who have been around since the 80s) are:

    struct listhead
        {
        listnode *firstnode; // "*" means, this is a pointer, dude.
        listnode *lastnode; // see above
        }
    struct listode
        {
        UInt foreignkey; // you may call this "Point ID" or "Vaclav"
        listnode *next; // pointer to next in this list
        listnode *previous; // pointer to Easterbunny
        }
    

    Iterating through a list of active particles would go something like this:

    for(listnode *active = mylisthead.firstnode;;active=active.next)
        {
    particleID=active.foreignkey;
        ... do some Fabric ...
        ... or some Magic ...
        if(active.next == mylisthead.lastnode) break;
        }
    

    And removing a particle from the roundrobbin, assuming you have the node of it in "active":

    active.previous.next=active.next;
    active.next.previous=active.previous;
    if(mylisthead.lastnode==active) mylisthead.lastnode=active.previous;
    if(mylisthead.firstnode==active) mylisthead.firstnode=active.next;
    

    Storing this "active" somewhere might then make sense, else, if you know you never want to reactivate a deactivated particle, just delete the node.

    Does this make sense or am I missing some obvious disadvantage of this? Again, the idea is to gain performance by avoiding calls to many deactivated particles. Of course this does not provide an indexing directly - but that could be achieved by a "node-pointing array" (I doubt it is necessary though).

    While rewriting large parts of the code I also want to create a better reflection/bouncing algorithm. The stuff I currently use is ... messy and does not really work that well. For the next iteration I would like to have both "bounce" ability AND rotation, so that a particle that has a spin can bounce of an obstacle and get the spin changed, if necessary.
    Does someone have some input on the math for this? Else I'll have to figure something out ... and I have no clue of maths :)

    I am not too sure if using Attributes on a Points geometry was such a good idea - although it seems "fitting", I can not currently use this system in modo (Fabric is attributeless in modo).
    I do see the advantage of integration into further Fabric development, naturally, but the question for me is: Is there active interest in using this stuff anywhere outside "non-academic research of ex-students of linguistics and germanistics about how to mess with their brains"? If this is more or less for myself, I'd definitely want to rewrite it without using attributes and probably even without using Points ...

    Anyway, sorry for another textwall and thank you for any input, including "go ahead, ignore its use for anyone else, LEARNING you must, old chap!" ...

    Marc


    Marc Albrecht - marc-albrecht.de - does things.

  • Roy NieterauRoy Nieterau Posts: 258 ✭✭✭
    edited March 2016

    I think a plugin should "require" a feature, so if, for example, a "heatspot" is plugged in, it should tell the simulation (init) that "temperature" is needed

    I'd do it so that the Heatspot can indeed require an Attribute, and otherwise it could create something with a default value.

    If you'd make a "HeatFalloff" plug-in that allows you to compute heat loss depending on how dense an area is with particles I'd give it an input attributeName on which it would base it's heat computation. This also allows us to perform some behavior on other custom attributes and makes clear that this attribute name is needed.

    Does this make sense or am I missing some obvious disadvantage of this?

    How does threading work with such a linked list? Since you can't easily index this you also can't do PEX operations on it?

    I am not too sure if using Attributes on a Points geometry was such a good idea - although it seems "fitting", I can not currently use this system in modo (Fabric is attributeless in modo).

    That sounds odd. The attributes on the Canvas Geometry itself run completely in Fabric Engine and doesn't require any feature from the DCC. So, where doesn't it actually work in Modo? I know it's beta, but I'd expect that to you just work fine already. Also, this is probably something that will work down the line for all integrations so I wouldn't keep it out of development for that reason unless you need the particle system right now!

    I do see the advantage of integration into further Fabric development, naturally, but the question for me is: Is there active interest in using this stuff anywhere outside "non-academic research of ex-students of linguistics and germanistics about how to mess with their brains"? If this is more or less for myself, I'd definitely want to rewrite it without using attributes and probably even without using Points ...

    Sure, you're testing and playing around so it's all up to you. :smile:

    The particles implementation from the other topic works very well with Attributes and I'd imagine it being the best way forward as it scales very well. It's user friendly, customizable... and works out of the box. I think the technique will serve you well down the line.

    Also this community loves to look at each other's work and fool around. That's why I also asked you about putting this on Github. It's a great way to all play around and share possible improvements back to the community. Collaboration. :smile:

  • malbrechtmalbrecht Fabric for Houdini Posts: 752 ✭✭✭

    damned ... that PEX ... that's a good argument against my beloved lists ... grummel (or is that to be spelled "grummle"?) I'll need to think about that, but I guess if PEX doesn't do lists, neither should I ...

    modo: Attributes will come, but unfortunately my time is rather limited, and right now I am splitting it between doing modo-fabric-tests and this project. I'd very much prefer doing both simultaneously ...

    Attributes: I don't disagree.
    I have to admit, though, that to me it feels much more "canvas-user-friendly", since on the code side you always have to get some ref pointers from some pointers first ... I accept, however, that Fabric is "graph"-ish. It really doesn't matter that I don't get the beauty of a graph compared to some lines of documented code. If I need to use three graph nodes to do what I can do in a single line of code, I need a lot more money for doing it in graph :blush:

    Github: I'll upload the heatspot thing from last night, but next iteration really should be clean up, I think.
    Your suggestion to make the attribute's dependencies user-changable is a good idea, that way plugins could get way more generic than the current "proof of concept" thingies. Noted for the rewrite :)

    Marc


    Marc Albrecht - marc-albrecht.de - does things.

13»
Sign In or Register to comment.