Tim's Particle System
by Tim Nikias Wenclawiak
Last update: 18.04.04
homepage: www.NoLights.de
First of all, the typical stuff
Please make sure that, when you're using these macros, my name and perhaps my homepage is mentioned somewhere in the credits, even if you're using modified versions of the macros. That is mostly more or less a moral issue. For people who actually make money in any way aided by these macros, I'd like to receive a note in which way, and would be happy to have a look at it. I don't expect anyone will actually make much money with these macros, but more with their own ideas, so I don't want to have people think I'd want money for this. I don't. What is the System designed to do? This Particle System provides some basic means for animation and simulation of particle-driven effects, e.g. smoke, rain and fire. You define groups of particles which are moved via macros and interact with the environment. Basically, you place a particle and give it a velocity, and the system will take care of moving the particle and letting it rebounce off of objects. There are various details to that process which make it possible to define completely different effects (like slowly dissipating smoke vs falling rain), but still keep everything running inside one system. |
User Defined Macros Group Creation Macros ParticleGroup_Begin & ParticleGroup_End ParticleGroup_Gravity - ParticleGroup_Damping ParticleGroup_LinearWind - ParticleGroup_TurbWind ParticleGroup_FXImpact & ParticleGroup_FXDeath Adding Particles Simulation Macro Displaying Macros Particle I/O Macros Particles_Get - Particles_Check Particles_Save & Particles_Load Particles_ResetAge - Particles_Add User Defined Macros [Macroname] (Particledata_Array) The visualization macro needs to have one parameter. This will be a one-dimensional-array of vectors that has been given to the system when using Add_Particle and gets modified whenever Parsys_Run is called. If the macro requires for example 6 indices, you have to provide them when adding the particle. For easier handling of this problem I've provided ParticleGroup_Datasize, which sets a minimum of indices a particle of a given group must have. [Macroname] (Particledata_Array, Time_Of_Impact, Pos_Of_Impact, Normal_Of_Surface) The impact-driven macro requires four parameters. As with the Visualization Macro, the first Parameter is the array of vectors which stores the state of the particle. The other three are pretty self-explanatory. Note that the four parameters are required even if it is used as a Death-Effect-Macro. This is done for consistancy and easy portability of different effects for different purposes. For a Death-Effect though, Pos_Of_Impact and Normal_Of_Surface are <0,0,0> unless called from within the object-interaction routines, in which case an object was responsible for the death of the particle and both those variables are at hand. [Macroname] (ParticleData_1, ParticleData_2, Sample_Number, Max_Samples, Position_Of_Sample, Direction_To_Sample) The sampling macro may be used in case one wishes to use different sampling techniques for the particle. Normally, a particle is simulated with one sample or a set of samples spread across the forward hemisphere of the particle. If the particle isn't spherical, this might lead to unrealistic results. In order to generate your own samples, the option for a sampling macro was realized. The macro is called with several parameters and should return a vector which is the current position of the sample by declaring Position_Of_Sample with the sample's position. In effect, the sample is returned via parameter modification. The first two parameters are the Particledata-Arrays before and after a given timestep. Sample_Number is a value which runs from 0 to Max_Samples. Additionally, the macro may generate its own direction to trace along, just #declare it inside the macro. If it is left at <0,0,0>, the system will trace along the direction the particle wants to move. Note that just by itself, the Sampling Macro might not have any great benefits aside that the object-interaction is more accurate to the form of the particle. But coupled with more specific Impact-Macros, it should be possible to realize rigidbody simulations, though this might be pretty complicated with the given confinements of a 1D-Vector-Array for the particle. I did want to have the possibility, but as of yet haven't done anything in that direction so far. It might happen that I realize a plug-in for that at some point. Group Creation Macros The creation of a particle group has to start with ParticleGroup_Begin and end with ParticleGroup_End. The first prepares several variables that will get carried along during the creation macros and keep track of different values, the last finalizes the group and prepares all macros and objects to be used by the simulation. Even a group with no modifiers needs ParticleGroup_Begin and -End. Not doing so can result in faulty behaviour or even stop rendering with an error. Also note that Macros will take effect on the particle in the order that they have been added to the Group. If, for example, a macro requires an initial velocity of the particle, but velocities are only added AFTER this macro has been processed, it may well happen that the macro doesn't seem to take place. Another example is the ParticleGroup_Damping, which dampens the velocity of a particle. If called somewhere in between other macros, the macros following the Damping-Call won't get dampened until the next timestep. ParticleGroup_Begin (Name_As_String, Detail-Settings) & ParticleGroup_End () ParticleGroup_Begin requires two parameters: the name of the group as string [e.g. ParticleGroup_Begin("smoke")], and the vector for the Detail-Settings of the group. ParticleGroup_End requires no parameter and will just finalize the group that was last begun with ParticleGroup_Begin. ParticleGroup_Datasize (Minimum_Size) Sets a minimum size for the array of vectors attached to a particle. If the array is smaller, it will get increased in size and filled with <0,0,0>. ParticleGroup_Object (Objectname, Rebounce, Damping, FX_Switch, Impact_Macro, Death_Macro) To add an object to the group for object-interaction, the macro requires several parameters. First, the name of the declared object as a string ("House", not House without quotes). The "Rebounce" is a value that sets the rebounce of the particle when hitting this object. 0 will modify the particle's direction to move along the surface, 1 will rebounce it. Values in between interpolate between those two. The only other value allowed aside of the 0-to-1-range is -1. When used, the particle doesn't rebounce anymore, but still causes a Macro-Call (if defined). "Damping" modifies the velocity of the particle after hitting the object, 0 stops the particle, 1 changes nothing, 2 doubles the velocity. Negative values are allowed only when "Rebounce" is set to -1. The FX_Switch has three possible values: 0, 1 and 2. 0 switches all impact-based-macros off, 1 uses the Impact_Macro and 2 the Death_Macro. In addition to using the macro, 2 will kill a particle when it hits the objects. Note that the macros will get called in any case, even if the particle already has its own inherant impact- and death-macros. Both Macronames, again, have to be given as strings ("Impact_Effect", not Impact_Effect without quotes). ParticleGroup_Gravity (Gravity_Value, Gravity_Direction) This adds a gravitational pull to particles. The Gravity_Value defines the amount of acceleration in the direction given by Gravity_Direction. ParticleGroup_Damping (Damping_Modifier) Modifies the velocity per second by the amount given by Damping_Modifier. Many simulation systems work with damping to gradually reduce velocities/forces in order to keep simulations from "exploding". Damping mostly also leads to an eventual dead-state in which nothing moves anymore, in this case, it can be used to model a crude sort of air-friction. ParticleGroup_LinearWind (Wind_Direction, Wind_Velocity, Friction) Particles will slowly accelerate to match Direction and Velocity of the wind. "Friction" defines a modifier how fast a particle matches this wind, value may range from 0 to 1. 0 will define vacuum, e.g. there is no friction and a particle may immediately match the speed and direction of the wind. 1 will define solid matter, e.g. the wind is never matched and doesn't even influence the particle. Note that when matching the wind, other forces like gravity may appear to not affect the particle anymore. This is especially true the lower the friction is. ParticleGroup_TurbWind (Lambda, Omega, Octaves, Scale, Pos_Modifier, Vel_Modifier, Friction) The Turbulenced Wind bases its directions on the turb_vect-function, the same function that turbulences textures, pigments etc. Lambda, Omega and Octaves are the typical variables used to control the "turbulenciness". The direction of the wind, as driven by the turbulence, slowly changes throughout the three dimensions, but is relatively random when just inspecting two further apart positions. In order to increase/decrease this effect, Scale may be used. Additionally, as the turbulence is based upon the current position of the particle, Pos_Modifier may be used to "translate" the turbulenced field.Vel_Modifiert modifies the velocity of the wind. Due to the pseudo-random nature, the velocity given by the turbulenced field varies greatly. If the effect is too much or less, the Vel_Modifier takes care of that. Finally, Friction defines how fast a particle will adapt to the speed, where 0 defines vacuum, e.g. a particle will immediately adapt to the wind, and 1 is solid matter (wind won't affect the particle). The lower the value is, the less other forces affect the particle. ParticleGroup_FXImpact & ParticleGroup_FXDeath [Either Macro] (FX_Macroname) Both Macros connect a certain event with a macro, one with an impact on an object, the other with the death of the particle. Both require a declared Macro, of which the name is given as a parameter (as a String, e.g. "Impact_Effect", not Impact_Effect without quotes). The Macros must have the amount of parameters as stated above at Impact-driven Macro. Note that even if an object defines Impact- or Death-Macro, then both will be called: the macro connected to the object AND the macro connected to the particle. ParticleGroup_Sampler (Sampling_Macro) Attaches a macro to a particle which is used instead of generating its own samples for the particle. The specs for the Sampling Macro can be found here. Adding Particles Add_Particle (Groupname, Particledata) With Groupname you define the group into which the particle is placed. The Particledata is a one-dimensional-array which has to follow a set of rules which are defined above. Simulation Macro Parsys_Run () This Macro simply traverses the active particles and simulates their behaviour according to their group (the process of tracking and storing the particles is quiet tedious and too much to explain here). The particles get loaded, modified/simulated and then saved back to disk. Displaying Macro Show_Particles (Groupname, Macroname) The particles of the given group will be visualized by the given macro (which has to comply to the specs of the Visualization Macro). In effect, the Macro will be called once per particle with the particle's data-array. If the group contains no particles, there's of course nothing to be visualized. Particle I/O Macros Particles_Get (Groupname) Once called, this macro will return a one-dimensional array. In it are stored one-dimensional-arrays, each one being the data of a single particle. Accessing the data of a particle is thus much like accessing a 2D-Array, but Povray won't issue a warning in case only one dimension is given. This is because in its essence, the array is 1D, with arrays as input. Referencing only the first dimension, e.g. Array[0] will return the 1D-Array of a particle's data. As this makes variable lengths of the second dimension possible, a special Save- and Load-Macro is supplied. Note that when calling Particles_Get before Parsys_Run, it will return the state of the particles before the simulation of this frame, and when calling it afterwards, the particles are in the state after the simulation of this frame. It might be easier to imagine that before is much like "beginning of frame" and after is much like "end of frame". Particles_Check (Array from Particles_Get) This macro checks if the Array returned by Particles_Get or loaded by Particles_Load is actually a valid particle-array or merely an empty hull. It returns a true/false. Particles_Save & Particles_Load Particles_Save (Array, Filename) & Particles_Load (Filename) Saves/loads the array returned by Particles_Get. Particles_Save will save the array into the specified file, Particles_Load will load from the specified file and return the array. Particles_ResetAge (Array, Moment_Of_Birth, Delta_Birth) When retrieving/loading the particles, their age and moment of birth are of course set to their original values. Thus, just introducing them back into the system would just send identical twins into the systems. When calling Particles_ResetAge, you define a moment of birth with the respective parameter. Additionally, you may provide Delta_Birth, which defines a maximum for a random value that will get added to the moment of birth, but gets randomized. The current age of a particle is automatically set to 0. Particles_Add (Groupname, Array) Much like Add_Particle, this macro will put the array of particles into the system and the defined group. Particles_Show (Array, Macroname) Similiar to Show_Particles, this macro will actually take the array retrieved by Particles_Get and visualize it with the defined Macro. |
Explanation and Example-Dissection Additionally to the basic effects provided with the basic release of the Particle System is an Effects-Development-Kit. This is a set of Macros which make it easy to create custom macros for the groups. To understand their functionality and get a grasp of their limitations, here's an insight into the inner workings of the Group-Creation: Once a group is begun with ParticleGroup_Begin, several files are prepared which are then finalized by ParticleGroup_End. One important file is "xxx.tmp", where "xxx" is the name of the group: it stores a series of POV-Ray commands like declarations and macro-calls. Essentially, that file is included for every particle of that group, and the declarations and macros modify the data of the particle. The EDK allows Usermade-Macros to be integrated into this file. Let's have a closer look at ParticleGroup_Damping for learning purposes: #macro ParticleGroup_Damping(_DampingAmount)
#end As we can clearly see, the ParsysEDK-Macros have a sortiment of commands. "AddMacro" most probably tells the System to add another macro of which the name is supplied as a parameter. Note that every macro which is to be used by the EDK needs at least two parameters: ParticleData, which is the 1D-Array of the particle's data, and TimeStep, which is the timestep that currently needs to be calculated. Since Damping requires a modifier, we add another parameter, since we can simply use a value (and not a variable, e.g. "Damping_Value" declared globally in the scene) we use AddParameterValue. We close the macro using CloseMacro and add a line feed just for a tidyness. Now, let's take a look at the actual Dampening-Macro, because obviously, ParticleGroup_Damping only prepares the Macro, but isn't the macro itself. #macro _VectorParticle_Damping(_Data,_TStep,_DampingAmount)
#end Now, we can see some actual POV-SDL taking effect. The first two parameters, as explained above, are the particle's data and the timestep, in this macro just named _Data and _TStep. The underscore isn't normally used in front of variable-names and hence should be safe to be used inside a macro in order not to overwrite or access other, global variables. The third parameter is the Damping-Modifier which we added in ParticleGroup_Damping. If we now take a look at the macro itself we can see how we create a local copy of the particle-data, modify it, and then replace the original data with the new data by declaring it. This is referred to as "returning via parameters" and can be looked up in the POV-Ray Docs as one of the two methods to return values from a macro. In this case, we modify the fourth index of the array (remember, arrays count from 0 onward), which is the velocity-vector. We subtract an amount which is in direct relation to the timestep and the damping-modifier. It is most crucial that macros take the timestep into account, as this may greatly differ during the simulation. The macro isn't called only once per particle, but may be called several times, depending on the Detail-Settings. If the effect of the macro wouldn't be dependant on the timestep, it's effect on a particle would differ depending on how detailed the calculation is. In this macro, timestep modifies the amount that is subtracted from the original velocity. In effect, the damping will affect a particle by the given modifier every second, because only when the timestep is 1 (one second) will the entire dampening-amount get subtracted. When building your own effects, there are few guidelines to follow in order not to screw up the Particle System's inner workings. From the basic four data-vectors it is advised to only modify velocity, though position is allowed as well. Do note though that the position will further get modified by adding the velocity to it by an amount defined by the timestep (e.g. the entire velocity-vector if timestep is 1, as the velocity defines the speed in seconds). Modifying the age and/or the size of a particle may produce unwanted side-effects like dissapearing and/or timewarping particles. Generally, it is advised to only modify the indices from 2 upwards, as 0 and 1 are the age- and size-vectors. Additionally, note that the basic macros only require the fundamental 4 indices, and you may simply build upon the system by using a larger array for a particle and using the added vectors for new functionality. In such a case, it is advised to make the index variable, e.g. by declaring a global-variable which tells the macro which index to use for its modification. Otherwise, it might be difficult to combine different effects when all of them make use of, for example, the fifth index, especially when they all make different use of that vector. ParsysEDK_AddParameterValue / -String / -Variable ParsysEDK_AddMacro (Macroname) The only parameter needed for this is the name of the macro as a string. It will add the beginning of the macro along with two minimum parameters, ParticleData and Timestep to the file. Note that when declaring your macro, the naming of the parameters doesn't matter as long as it is consistant within the macro. But since the local variables used by the Particle System are named _tmp_Data and _tmp_TStep, these shouldn't be used. ParsysEDK_AddParameterValue, ParsysEDK_AddParameterString, ParsysEDK_AddParameterVariable [Either Macro] (Value/String/Variable) Will add another parameter to the macro. Note that "String" and "Variable" both are strings with quotes, but ParsysEDK_AddParameterVariable won't save the quotes in order for POV-Ray to understand the parameter as the globally declared variable. ParsysEDK_CloseMacro () Places the closing bracket of a macro and thus finalizes it. ParsysEDK_AddDeclarationValue, ParsysEDK_AddDeclarationString, ParsysEDK_AddDeclarationVariable [Either Macro] (Name_Of_Variable, Value_Of_Variable) These macros use the Name_Of_Variable (a string) and use it for declaring it. The value assigned is Value_Of_Variable. Much like the AddParameter-Bunch, Value just has to be value, whereas String and Variable are both strings, but Variable doesn't save the quotes. In effect, the result is much like this: #declare Name_Of_Variable = Value_Of_Variable; ParsysEDK_AddLineFeed() Just adds a simple linefeed to the file in case you want to separate your macros visually from the others. Not really useful unless for debugging purposes. ParsysEDK_AddSDL (POV-SDL as String) In case you want to add more complicated scripts into the file (which isn't advised but may be needed), this macro will simply write the given string to the file as POV-SDL. Note the complications that arise when using quotes inside of strings (e.g. "this is \"quoted\", got it?") and linefeeds. If the String is too long and POV issues a warning, just use this macro twice instead of just once. It doesn't add any default linefeeds. |