RSS< Twitter< etc

<< Back To Krakatoa Tutorials

Distributing PRT Maker Particles On A Geometry Surface

Applicable to Krakatoa v2.0.0 and higher

Overview

The PRT Maker introduced in Krakatoa MX v2.0.0 allows the free distribution of particles using a Magma modifier, as already shown in a previous video tutorial.

Logically, it would be very useful to apply the object-related operators found in Magma to distribute particles on geometry surfaces as an alternative to the Particle Flow Surface operator.

In the following tutorial, we will trace the steps necessary to calculate positions on mesh faces and discuss calculation and use of Barcycentric coordinates and the evaluation of the face area to ensure equal distribution. We will also see how excess particles can be deleted from inside the Magma modifier.

The Basic Setup 

  • We will start a new fresh 3ds Max scene and will create a Plane primitive with 100x100 dimensions and the default 4x4 segments.
  • In order to see the actual faces better, we can add an EditMesh modifier to the stack, select all Edges and change them to Visible.
  • We will also need a PRT Maker on the side. By default it creates 1000 particles at the object's origin. 

Adding the Magma Modifier

For our surface placement, we will need a Magma modifier on the stack. 

  • Select the PRT Maker.
  • Click the KCM icon (if you have a Krakatoa toolbar), or select "Add Krakatoa Channels Modifier..." from the Krakatoa menu - a new Magma 2.0 modifier will be added to the PRT Maker.
  • Click the Open Magma Editor button.
  • Press Ctrl+O to create a new Output node and select "Position" from the channels list.
  • Press O for Objects category, and Q for FaceQuery operator. The new operator has several inputs, but for now we need only the Geometry Input connected.
  • Drag a wire from the Geometry input socket of the FaceQuery operator and release over an empty spot of the editor - a new Geometry Input node will be created.
  • In the Geometry Input node, click the button Pick In Scene... and click the Plane object.
  • Note that the ObjIndex of the FaceQuery is 0 which corresponds to the first object on the Geometry Input list, the FaceIndex is also 0 which corresponds to the first face of the Plane, and BaryCoords defaults to 0.3333 for X, Y and Z, which represents the exact center of the face.
  • Make sure the FaceQuery's Position output socket is connected to the Output node. 
  • Since the Position of FaceQuery is calculated in World Space, but the PRT Maker's stack is evaluated in Object space, we need to convert the result from World to Object space - select the FaceQuery node or the wire connecting it to the Output channel and press T for Transform and W for FromWorld. 

At this point, the Position of the Center of the First Face in the Plane will be assigned to ALL 1000 particles of the PRT Maker:

Iterating Through The Faces

Instead of using Face 0 for all particles, let's place one particle in the center of each face. 

  • Press I for Input and C for Channel, then select "Index" from the list of channel names.
  • Connect the Index channel input to the FaceIndex socket of the FaceQuery operator. 

The Index channel is not really a channel that exists in memory, it is an internal channel to the Magma modifier that counts through the particles as they pass through the modifier. Now we have each particle sent to the face with the corresponding index, which places the first 32 particles on the 32 (4x4*2) faces of the Plane. The rest of the particles land at the world origin because the FaceIndex is out of range and the FaceQuery returns a default [0,0,0] value:

 

Dealing With The Excess Particles

We have 1000 particles, but only needed 32. We cannot just set the PRT Maker to 32 because it would not adapt well to any changes in face counts. Instead, we want to produce a lot more particles than we need (since this is quite fast), and then get rid of the additional particles.

Here is one way of doing it: 

  • Press O for Object and M for MeshQuery - this operator provides access to the FaceCount of geometry objects.
  • Connect the InputGeometry node to the MeshQuery operatror.
  • With the MeshQuery operator selected, press L for Logic and O for GreaterOrEqual operator - it will be connected to the MeshQuery. 
  • Press Ctrl+W to swap the two inputs and make the FaceQuery FaceCount the second input.
  • Connect the existing Index InputChannel to the first socket.
  • Press Ctrl+O to create another Output node and set it to Selection channel.
  • Connnect the GreaterOrEqual to the new Output - this will be invalid because the Logic operators output 0 or 1 for False/True, but the Selection wants a Float.
  • Select the GreaterOrEqual operator and press the Convert To Float button in its command panel.
  • Add a Krakatoa Delete modifier to the top of the stack!

At this point, particles with Index equal or greater than the number of faces in the geometry will be set to Selected, and the Krakatoa Delete will delete them - note the PRT Maker now shows only 32 particles! 

Preserving All Particles And Wrapping Around The Index

Since we want to place more than one particle per face, instead of deleting the particles once they reach the face count, we can just wrap around and start counting again. 

  • Delete the GreaterOrEqial, ToFloat and Selection Output nodes.
  • Select the existing Index InputChannel operator and press A for Arithmetic and M for Modulo.
  • Connect the existing MeshQuery's FaceCount output to the second socket of the Modulo operator.

At this point, the Modulo operator will output the numbers 0,1,2...30,31 and then again 0,1,2...30,31 because it produces the remainder of the division of the Index by the FaceCount. All 1000 particles are placed again and again at the centers of the same faces:

Distribution At Random Barycentric Coordinates

 Instead of placing each particle at the exact center of each face, let's calculate a random position within the face and put the particle there. 

  • Press I for Input and C for Channel, then select "RandomValue" from the list. RandomValue is a channel generated exclusively by the PRT Maker based off the Random Seed exposed in the base object's UI. Each particle gets its own unqiue random value.
  • To create a random position, we will use a BlackOp compound called "BaryFromRnd" which expects a single random input value and produces 3 random Barycentric coordinates as output. This BLOP ships with Krakatoa MX v2.0.1, but we will take a look at its content next.
  • We connect the RandomValue input to the only socket of the BaryFromRnd BLOP and connect the output of the latter into the BaryCoord socket of the FaceQuery.
  • We can increase the particle count in the PRT Maker Base Object to 10,000 to have more particles to show! 

As result, each pass through the faces places the particle somewhere else, producing a random distribution!

The Random Barycentric Coordinates BLOP

Let's take a look at the content of the BaryFromRnd BLOP and see how a single RandomValue is used to generate a Vector value with 3 components.

It is important to note that Barycentric coordinates have 3 components that always add up to 1.0. They represent the ratio of the triangles connecting a point inside the triangle with its corners divided by the full area of the triangle. Thus, it is not possible to have the point inside the face so that the ratio of the sub-triangle and the full triangle exceeds 1.0, and the sum of the 3 triangle areas divided by the whole triangle is obviously always 1.0.

We have one random value, we only need a second one, and then we can easily caclulate the third component by subtracting the sum of the two from 1.0.

There are two possible approaches to get a second set of random numbers - a Noise function, or using a portion of the existing RandomValue.

This BLOP uses the latter method. The idea is that if you have a random value like 0.36412958, all digits behind the decimal point are random, and thus any portion of them could be used as a new random number. 

For example, multiplying the above number by 1000 will give us 364.12958. Subtracting from this number the rounded down portion of the value (364) produces a value of 0.12958 which together with 0.36412958 can be used to produce the 3rd component by subtacting both from 1.0, or 0.50629042.

We also have to take into account the case where the sum of the first two components is greater than 1.0. In that case, we take both their values as 1.0-X.

The following BLOP flow implements the above logic:

Non-Uniform Face Areas

In our simple test case, the distribution looked good because each face in the Plane had the same area. But had we used a Teapot, the larger faces would have looked less dense than the small faces on the lid because each face would get the same number of particles, but the face areas would be vastly different. 

  • Go into the MeshSelect modifier, switch to Vertex Level and scale 4 vertices of a quad to produce two much larger faces, with the faces surrounding them becoming much smaller. 

As you can see, our current setup does not handle this case well:

Adjusting The Particle Distribution By Face Area

In order to produce equal distribution on all faces, we will have to calculate the area of each face, then calculate the number of particles per area unit (Square Generic Units in 3ds Max) and delete any particles that are above the required count for each face.

We will use a BlackOp Component operator called "FaceArea" which gets as input the Geometry Object, the ObjIndex and the FaceIndex and outputs the area of the given face. The content of the BLOP will be discussed in detail later. 

  • Create a FaceArea BLOP by pressing B for BLOP, G for Geometry sub-category and A for FaceArea operator.
  • Connect the InputGeometry node into the Geometry socket.
  • Press 0 (zero) to create an InputValue with integer value of 0 into the ObjIndex socket.
  • Connect the Modulo output (the face index counter) into the FaceIndex socket.
  • Selec the FaceArea BLOP and press * to create a Multiply operator. Leave the second input at 1.0.
  • Press L for Logic and O for GreaterOrEqual the way we did in the previous section about deleting particles.
  • Connect the output of the new Multiply operator into the second socket of the GreaterOrEqual.
  • Press Ctrl+O to create a new Output and set to Selection channel.
  • Convert the GreaterOrEqual to Float like before.
  • Create a Divide operator by pressing /, then connect the InputChannel Index into the first socket and the MeshQuery FaceCount into the second.
  • Select the first wire and press C for Convert and F for ToFloat, then releat with the second wire - the division should be performed with Floats instead of Integers!
  • Connect the output of the Divide operator to the first socket of the GreaterOrEqual.

 At this point, we are calculating the ratio of particles (Index channel) and Face Count which gives us the number of iterations within the current face. We compare that number with the face area in square units and if the value is greater, we set the Selection of the particle so it can be deleted. For example, if a face has an area of 10 square units, 10 particles will be distributed randomly and the 11th and any above that will be deleted.

When using 10,000 particles, it turns out we don't have enough iterations to fill the two large faces - they still look less dense after 8605 particles have been placed:

 

But if we would increase the Particle Count to 50,000, we now have enough particles for all faces with 10,001 particles now placed and 40,000 deleted:

The reason for the count of 10,001 is that we created our Plane with Dimensions of 100x100 = 10,000!

Controlling The Distribution Density

We added a Multiply operator after the FaceArea calculation, but it is not used yet (it multiplies by 1.0).

We can connect and expose a Float InputValue node to control the actual density: 

  • Select the Multipl operator after FaceArea and press Ctrl+1 to create a new Float InputValue node.
  • Click the Expose checkbox in the Magma Command Panel.
  • Change the name of the node to "Area Density"
  • Enter a value of 0.5 in the 3ds Max Command Panel 

The FaceArea BLOP Content

Before we finish, let's take a look at the FaceArea BLOP that calculates the area of each face. It ships with v2.0.1 of Krakatoa MX.

We use the fact that the Barycentric Coordinates of the 3 face vertices are always [1,0,0], [0,1,0] and [0,0,1], and that the Face Area is half the magnitude of the cross product of two edges. 

  • We calculate the Vertex1, Vertex2 and Vertex3 positions using the above barycentric coordinates.
  • Then we subtract Vertex2 from Vertex1 and Vertex3 from Vertex1 to produce two edge vectors.
  • Then we calculate the VectorCross product, take its Magnitude and divide by 2 and output the result as the Face Area: