Thursday, 27 February 2014

Thinking Particles: "Conveyor belt" setup: Basic

This is a setup for creating a conveyor belt like effect in Thinking Particles. In the video, as a teapot enters the box point it triggers an event, and a particle is spawned at the beginning of a path and then all the particles on the path are pushed along. Additionally, there are particles that are spawned along the path so you don't need to pre-roll particles.



Basic scene setup:

A teapot to use as a shape instance, a path to act as the conveyor belt, a point to spawn trigger particles, and a point they can move into to activate a slide of the conveyor belt.



Method:

When an event is triggered, a particle is spawned and has a Path Follow applied to it. It is then given a burst of speed when it is born that lasts for a few frames. This pushes it along the path. Once this time is up, the particle is given a very tiny speed as to maintain its direction.

Groups:

Spawn_Trigger_Particles:
Spawn_Path:

Dynamics:

Spawn_Trigger_Particles:
     Spawns particles at the cross point. Particles are put into the Spawn_Trigger_Particles group.



Spawn_Pre_Path:
     Spawns particles only at the first frame, so that there are particles already spread out on the path. Particles are put into the Spawn_Path group.



Assign_Shape:
     Something nice to look at.



Send_To_Trigger:
     Bring To that attracts the Spawn_Trigger_Particles to the box point. Once they are close enough to the point, a particle is created in the Spawn_Path group.



Path_Velocity:
     This controls the speed of the particles along the path. This has to be before the path follow or the particles will not stick to the path. The idea is to have the particle that is created move for a short time then stop. I used a particle age to create an on/off boolean that I use to determine speed. Whilst the particle is within the particle age, there is a speed of 1, when it falls out of the range the speed hits 0, and the particle stops. By adding a multiplier I can increase the speed the particle gets.

However, in order to maintain the direction of the particles, the speed cannot get to 0, as the vector is lost. To maintain the direction along the path therefore, I add a tiny amount of speed after the on/off velocity to keep that vector. The speed is so slow you will not notice any movement, but stops the particles getting "stuck" when the speed hits zero.



Path_Push_Along_Others:
     This PPass finds the particle next to them and inherits their speed. This means when a particle is created and starts moving, it will push the others along. The Expression works out the speed of the particle from the particle velocity using Pythagoras' Theorem, which is then put through a Threshold to determine if it is above the tiny amount of speed set earlier (effectively the "no speed"). This means only particles with speed are allowed to transfer their speed, otherwise the particle that is created will inherit the "no speed" of the particle in front of it and it will not move.

The expression:
sqrt(a^2 + b^2 + c^2)




Path_Follow:
     Constrains the particles to a path. The Path_Follow has the Initial Speed set as defined. I also set Multiple Particles to Defined at a Distance of 10, as the radius of my teapot was 5 (diameter 10). This ensure the particles are evenly spaced along the line.


Thinking Particles: Scale an object down to nothing over time

This is a setup I came up with for scaling down a shape inside TP to nothing and then deleting the particle, so that the particles disappears smoothly and doesn't suddenly vanish.



Basic scene setup:

A cross point for spawning particles and a box point for the particles to travel to that also triggers the scale down event once a particle reaches it.


Method:

Once a particle gets close to the point, it triggers the start of a Timer. This determines how many frames it takes for the particle to scale down and die. The scale of the particles is multiplied by the complement of the Time Relative output from the Timer node, allowing it to scale from 1 to 0 over the timer frames. Finally there is a threshold to detect when the particle gets small enough to be sent to be deleted.

Groups:

Spawn_Trigger_Particles:
     Just one particle group. Name was left over from another TP Dynamic.

Dynamics:

Spawn_Particles:
     Particles are spawned at the cross point.


Assign_Shape:
   Shape assigned for easier viewing.


Scale_Down:
     The particles are attracted to the box point by using a BringTo. It also has a Distance condition, so that when the particle gets within a certain range using the Threshold, they start a Timer (The Timer is set to 30 frames). This is where the "magic" happens. The Time Relative output of the Timer node outputs a normalized value between 0 and 1 over the Timer range [0 at frame 0, 0.5 at frame 15, 1 at frame 30]. This is the opposite of what we want, we need a range between 1 and 0 for our scale down. To do this I created an Expression node and setup a "complement" using the following expression:

1 - a

Now this can be plugged into the Scale Factor input of a Scale node. Finally I used a Threshold node such that when the complement goes below 0.001 (an amount where I consider the particle is small enough to delete) it activates a Particle Die. It is worth noting you need to plug the Particle output from the timer node into these nodes and not from the PPass.


You could adapt this effect quite a bit as need be. You can have the time start when particles enter a new group. You could also add a Random node to the Timer Frames to get different lengths of scaling down.

Wednesday, 26 February 2014

Thinking Particles: Bullet: Cancerous growth

This is a Thinking Particles setup to create a cancerous growth like effect. It is made possible mainly due to how stable bullet is, allowing particles to spawn on top of each other without causing an "explosion" of the simulated objects.



Basic scene setup:

A box for the ground plane, a point that can be used to position the first particle, a spherical gravity to hold the particles together and a TP object.


Method:

The method involves having a parent particle which spawns two child particles at its position (after a random time has elapsed) before being killed. This leaves two particles in its place, which then under the bullet solver, spread out and these become parent particles that will go onto spawn their own children again. This creates a loop that will keep "dividing" the particles at an exponential rate.

Groups:

Active:
     All children in this group are subject to the forces and bullet.
Wait:
     Particles are in until timer is up and they are sent to divide. All particles are spawned into this group.
To_Divide:
     When sent to this group the particles activate the spawning of two new particles and its particle death.
Stop_Divide:
     Sent to this group once a particle number count is reached to stop particles dividing forever.

Dynamics:

Spawn_Initial:
     A single particle is spawned at the point in the scene. This is sent to the Wait group.


Assign_Shape:
     Where a shape can be assigned to particles. In this case i used a geosphere.


Wait_to_Divide:
     This takes the Wait particle and assigns it a random number between 25 and 90. When the particle age reaches this random number, it is sent into the To_Divide group.


Divide:
     The To_Divide particle spawns 2 particles with its position and alignment. It then dies after a time of 1 frame. If this was not here, there would be a frame before the new particles are born and after the To_Divide particle dies where there was no particle at all. The new particles are spawned into the Wait group, creating a loop between this and the Wait_to_Divide group.


Stop_Divide:
     This is an additional feature where the particles will be sent to the Stop_Divide group once the "Max Cells" count is met. This prevents the Divide group from spawning anymore, stopping the particle division. This is done using a PSearch with a very high max radius, as to find all particles in the scene. It checks the "Found Count" and compares it to the "Max Count" minus 2 using a threshold. If this is above the threshold it is sent to the Stop_Divide group.


The reason you subtract 2 from the "Max Cells" count is firstly you have to subtract 1 to compensate for the search particle not including itself in the count. Secondly, the threshold only triggers when it is over the value not equal to. So if your max count was 4, and there were 4 particles in your scene, it would not trigger as 4 is not above the 4 threshold (think count > 4, not count => 4). This is why another 1 has to be subtracted.



Forces:
     Where the spherical gravity is applied as well as spin and friction. The friction can be used to control how fast the particles "divide".


Bullet_Solver:
     Where the bullet solver is applied, and the floor plane added.


Active groups Bullet setup:

For all the groups under the Active group, I created a BulletPhysics parameter and set it to use a sphere shape. This is important. The sphere is very fast to simulate and very stable when spawning on each other. It also means you can have an assigned shape that is not a sphere and the TP would still work.


Fusion: Adding a curve multiplier to motion blur

This is a method I came up with to tackle quite a unique problem: I needed to adjust the falloff of motion blur, so where there wasn't much movement, the motion blur was lowered even more, but where there was fast movement the motion blur kept its intensity.

Note: This is working with post motion blur, created from a velocity pass. In this case in Fusion, using the "Vector Motion Blur" node. I used an unclamped velocity pass, so the range has not been normalized. I am also using the Krokodove plugin for Fusion, specifically the "Vector Visualization" node so I can show feedback on the motion blur vectors.

This is the velocity I will be using for this example. You can see a normalized version under it. It has red going in the x from -10 to 10, and green going from -10 to 10 in the y.


OK, first we need to understand how a velocity pass works. It is a data pass that stores how much a pixel moves both horizontally (x) and vertically (y). This is stored as red and green values within the image. These values help calculate the direction and magnitude of the motion blur. You can see for yourself by using Pythagoras' Theorem:
a^2 + b^2 = c^2

So you cannot simply add a curve to the velocity pass, as you would actually end up changing the direction of the vectors.

Original Vectors:


In the image below, the red is original vector and the green is the adjusted. You can see the deviation in the vectors:


This is because the red and green values are not going to be the same values in most cases. In the vector below you can see it goes diagonally across. It has a longer x value to produce the sloping angle.


If you wanted to half the vector using a clamp, and had the maximum threshold set to 4, it would pick up the y value but not the x. By changing only the y value, you are changing the direction of the vector, as well as not scaling the magnitude correctly:


What you really want to do is scale both values. The way to do this is to clamp the magnitude produced, not the x and y components.


To do this we work out the magnitude by using Pythagoras' Theorem. I used a custom tool in Fusion to do this.

Next I normalize the values to whatever I want the maximum velocity affected, and clamp the rest, so there are no values above 1 (this is important for when i multiply the curve onto the velocity pass later)

Then we can use a curve on this data, and then use this as a multiplier for velocity (red/green data).
If we have a curve that goes from 1,1 then the output will be unchanged, and from there we can draw a curve into the magnitude based on intensity.

Finally you can multiple this new pass with the velocity pass, and it will multiply the red and green in each pixel the same amount, scaling down the velocity correctly

Vectors with 1 to 1 curve:


Vectors with falloff (green are the original vectors, red the new vectors):


Fusion setup:


And expression used in nodes:


Tuesday, 25 February 2014

Maxscript: Resizing text to fit within a boundary

I was asked the other day if I could make a way of resizing text inside 3dsMax to fit the width of an object; in this case a 2D rectangle primitive, so that the rectangle could be positioned and then different language texts could be resized to fit within the predetermined area. As you may know, the text object inside 3dsMax constantly expands as you write text and there is no way of constraining it to a certain width.

Here is the final script in action:


Like most things, first I figured out how I could make a system that did this, then I created a maxscript so other people could operate it and I didn't have to perform the setup each time.

So what needs to be done?

You want to take the width of the text (textWidth) and scale it by a number such that it equals the width of the target object (targetWidth)

You can do this by:

targetWidth/textWidth = scaleFactor
finds the mutiple needed to get the textWidth to be the same value as the targetWidth



textWidth * scaleFactor
makes the textWidth the same value as the targetWidth by multiplying by the scaleFactor

So how can we perform this in 3dsMax?

First we need the width of the targetWidth. After a quick Google, I found you can get the boundary box width of an object through an Expose Transform tool. Although you could also use the width value of the rectangle, this method means you could apply it to a different shape easily if need be.


Now we need the textWidth. Again I can use an Expose Transform.

So if I have for example, text that was 200 wide that needed to fit within a space that is 100 wide, we would get:

100/200 = 0.5
targetWidth/textWidth = scaleFactor

We can check this. If we take 200 and multiply by 0.5 we get 100 which is correct.

As I don't necessarily want to affect the scale of the text, the scale factor would also work with the size parameter, as it is merely a multiple of what needs to be applied.

This is the final maxscript I created for the function.


Finally add a simple UI and you have a nice little script: