5. October 2023

Using loops for modeling

When it comes to building your own geometry modifier there are few ways around processing single elements. If just putting together multiple existing modeling operations doesn’t cut it, you will likely need to use iterations and loops to build your own.

There are many ways to manipulate geometry, let’s start with a straight forward example

Simple point displacement

Points are probably the most basic and easiest to modify elements within a geometry, most deformers are based on them.

In this example we want to move points according to a noise, so basically a displacement deformer with noise as the function.

Let’s take a quick look at what we want to achieve

In this case we have three parts that compose the whole modifier. From left to right we have

Retrieve Data

This is where we provide all the data needed for the operation. In this case we use Geometry Property Get to retrieve the complete array of point positions, as well as Points Info to retrieve the information of which of the points are actually selected and the positions of these selected points.

We get the Point Positions from Points Info, but we could get them from Geometry Property Get as well, this makes no difference in how the setup works.

Iteration

This is where we process the selected points.

Iterate Collection delivers one index of a selected point after the other. It is important to note that the Index output of the node refers to the index of the iteration, NOT the index of the point. It is the Elements output that delivers all elements of the array that we put into the node, so in this case this delivers the indexes of all selected points.

Since the Positions output of the Points Info node holds only the positions of the selected points we need to use the iteration index to retrieve the position that matches the currently iterated point index.

The first selected point in the Point Indexes array has the iteration index 0, as has the first position in the Positions array.

Now that we have the point position we sample our noise and map the result to our displacement strength using the Range Mapper node. The noise has an output between 0 and 1, so this is what we use on the input side of the Range Mapper.

Since this is a very basic noise deformer we only add the displacement noise to the Y axis. A slightly more complex setup could do this along the normal of the point, but this example is about the iteration, not a fully fledged displacement setup.

Having calculated a new position for the point we write this information back into the array of positions we retrieved from Geometry Property Get. It is important to note that we use the points index we retrieved using our Iterate Collection node, which is provided at the Elements output port of that node.

To make sure the iteration is correctly processed we define the scope of it by connecting the {… port of the Iterate Collection Node with the …} port of the Set Element node. This defines which nodes are within the iteration and which are not. While the Scene Nodes can often find these iteration scopes correctly on their own so setups without this connection can work, it is good practice to clearly define the scope since later on in more complex setups with multiple nested iterations things might not be as clear. Do yourself a favor and always take care of this.

Write back changed Data

The last step is to write back the changed positions into the original geometry data, which is easily done using the Geometry Property Set node.

If you want to make this a usable asset, and not only an example, you can expose parameters of the noise and the displacement values defined in the Range Mapper.

You can find an example on how to do this here.

https://nodebase.info/wp-content/uploads/2023/10/Iterations-for-Modeling-01.c4d

Noise based extrusion

Points were easy to modify because they are such a basic element of geometry. This is different for Polygons since they are defined by a topology and require multiple points. An operation like an extrusion requires a change to the whole geometry data. For point displacement it was sufficient to just change the data for that point, that doesn’t work in this case.

Again let’s take a look at what we want to achieve, a noise based extrusion of selected polygons

Usually extrusion only works with a single extrusion value for all selected polygons, this is what you get if you just use the Extrusion node on a selection of polygons. To individually modify the offset, or any other value, of the extrusion we need to process each polygon individually, which means that we have to modify the geometry multiple times, one after the other, different to the previous example where we did all our position manipulation on the array of positions and then wrote the result back in one single step.

To process geometry multiple times we have to use a Loop Carried Value node. This node has the distinction that it carries the result, value, of an execution over to the next execution. It needs an iterator to work since that defines the looping. There is a related node, the Memory node, which doesn’t require an iterator but uses the animation time instead.

The way it works is actually very simple, every change you do to the data in the loop variable(s) will be available on the input side for the next iteration step and so on.

The basic setup we see is the same as for the Point Displacement we did before. We retrieve the selected Polygons and iterate over their indexes, then we sample a noise value for the position of the center of the polygon and map it on an offset/displacement value. What changes is the last part.

The Loop Carried Value node is primed with the original geometry, which is connected as a Variable. Only Variables are carried from loop iteration to loop iteration. You can define them on the LCV node

The Selection String (index of the currently iterated polygon), the Pivot Matrix (center of the current polygon) and the offset for the extrusion of that polygon are simple input ports. Not the iteration scope connections {… …} that define the loop the LCV node runs through, together with the Get Element, Sample Noise and Range Mapper node. For every iteration step a new polygon index, pivot matrix and offset are provided.

This is what the inside of the LCV node looks like. Additionally to the extrusion there is a Transform Geometry node that will scale down the extruded polygon slightly to give it a bit of a pyramidal shape. This is what we need the polygon center for to make sure the scaling happens in the right location (Pivot Matrix).

The main action is the Extrude node. It receives the current state of the whole geometry from the Previous>Variable 1 port, which for the first iteration is the original geometry we start with. After the extrusion of the currently iterated polygon is done, we use Selection String to define that, the result is forwarded to the transform and then to the Next>Variable 1 port, which makes sure that this changed geometry will be the starting point for the next loop iteration.

A point of note, while extrusions change the topology of the geometry they do not change the polygon indexes of the polygons we start with, every polygon added by the extrusion process is added at the end of the list of polygons. This allows us to just go through our original polygon selection, knowing that the indexes remain valid through out the modeling process. This is different if we were to use an operation like delete or subdivide, in that case the original polygon is either not there anymore, or is replaced by multiple polygons, there is no simple solution for those situations a new solution has to be found case by case.

https://nodebase.info/wp-content/uploads/2023/10/Iterations-for-Modeling-02.c4d

Take aways

  • Modeling operations that require processing of the whole geometry need to be iterated using a Loop Carried Value node.
  • Applying modeling operations in an iterative way can become very slow, depending on the operation and the number of iterations.
  • Modeling operations that change the topology of the geometry by deleting or changing the processed elements need extra considerations to ensure that the data for each looping iteration is still valid.