Out next data type is Transformation. It’s designated by the light green color. It’s primarily used for specifying the placement of 3D objects. In most cases, it expresses a triplet of a Position, a Rotation, and Scaling information. It can also take on the format of a matrix to express more complex data, for e.g. the perspective transformation of a camera, but these cases won’t be discussed in this tutorial.
We have a very simple scene here that merely consists of a cube and a camera looking at it. I expose the camera so that we can see it. Currently, we can see the cube from its side. The cube is connected to a Scene Node. By changing the Transformation property of the Scene Node we can modify the placement of the cube.
Let’s see the editor of the Transformation. We can see the mentioned Position, Rotation, and Scaling triplet. Each of them consists of a maximum of three components which can be edited in the same way as any other numeric value. Let’s shift the position of the cube in the direction of the X axis and then the Y axis. The Rotation row expresses the rotations around the three axes so let’s rotate it around the X axis, the Y axis and the Z axis. The Scaling determines the magnification of the objects. Currently, it is in Uniform mode that means that the scaling is equal along all three axis and specified by a single number. But we can switch the editor into a different mode using the little arrow on the right side. If we choose the Pos-Rot-ScalXYZ mode, the Scaling row also splits into three fields, and we can specify different scales along the three axes. We have to be careful here because if we switch back to the Uniform mode, the scalings specified for the Y and Z axes will be lost. The third mode we can choose is Matrix. In most cases it’s impractical for editing, so we won’t go into the details here.
There are several operations that can be applied to Transformation values, and also a transformation can be built up in several ways. One way is to simply edit the value on the Pin Values panel as we’ve seen. But in the cases of certain types of animations it’s worth applying a different approach: build the final transformation as a series of elementary transformations. For example, if we want an object to orbit around a center. This can be done to first perform a translation (or movement in other words) and then a rotation. There are several Translation modules, some for a single axis, some for all three axes. Now we need an offset in the Z direction.
This module has a Delta property that specifies the amount of the offset. Its output is a Transformation that expresses an offset along the Z axis. So if I wire it into the scene node, it will replace the node with the specified amount. Now we have to insert a rotation around the Y axis after the Translation. If I wire it into the Scene Node by itself, it rotates the object around its own axis by the specified Angle value. But if I apply it after the Translation, it will rotate around the same original axis, which results in an orbiting animation.
There is a more intuitive alternative to the numeric editing of the Transformation. This is displaying graphical arrows or rotating rings and doing the movements by dragging them. This kind of scene editing is discussed in several other tutorials.
The next data type is the Collection. It’s designated by the orange color. It’s basically a collection of an arbitrary number of named values. It enables retrieving the desired value by its name. The values themselves can be scalars, integers, logicals, or texts, but they can be subcollections as well. This way a hierarchical data structure can be achieved.
A Collection pin is not able to be edited on the Pin Values panel. There are two ways in which this type of data can appear within the compound. One way is that there are special modules that provide their complex output in the form of a collection. For e.g. the modules that read rows from an SQL database. The other way is to import an XML or a JSON file. For e.g. let’s drag-n-drop an example JSON file into the Flow Editor. Now we have its data content from its form of a Collection.
The JSON file is also loaded into an external text editor. We can see a hierarchical structure here. It is converted to a collection in the following manner. First, a subcollection is created with the name “widget”. Within it a text value “on” is added with the name “debug”. Then a further subcollection is added with the name “window”, then several text and numeric values with the names “title”, “name” etc. And so on.
Suppose we want to retrieve the “width” value of the “window” object. We can do it by simply specifying a path to that value. The retrieving can be done by using several Collection modules. Since we want to access a numeric value we have to use a Collection Scalar module. We wire the collection into this module then specify the path to the value which is widget/window/width. On the output of the module, we instantly get the value 500.
What if we want to retrieve multiple values from this “window” object? Of course, we can do it by repeating the same path. But we want a more convenient way where we handle the object as a separate unit to access its own data. We have the Subcollection module for this purpose. Here we can specify a path of the subcollection we want to separate. In this case, it is a widget/window. On the output of the module, we can see that it only contains the data fields of the subcollection. Now if we want to retrieve the data “name” - which is text data in this case - we add a Collection Text module, and only specify a relative path which is simply “name” in this case. Now we have the desired data content.
The advantage we’ve already seen with text files also applies here. If we modify the content “name” to “other window”, and then save the file, the system reimports the file automatically, and our Collection Text module already gives the new value.
How is it handled if the JSON file contains an array? Let’s create one quickly with a few numbers. Here we have the module we previously used to retrieve the “width” data. It still gives the value 500, which is the first element of the array. But if we modify the Index property of the module, for e.g. let’s make it 1, then the value with the given index will be retrieved. In this case, it’s the 43, Index 2 is 69, and so on. If entire subobjects are organized into arrays - which is possible in JSON - then we can use the Index property of the Subcollection module in the same way.
If the JSON file contains an error, for e.g. if I forget the comma here at the end of the array, we get an error message in the log complaining about the missing comma. The JSON module itself gives an empty collection in this case.
If we make a mistake within the flow editor, for e.g. we misspell a path, we type “wind” instead of “window”, then firstly the Exists output pin of the Subcollection module will change to OFF designating that the data doesn’t exist, secondly, it gives an empty collection. Of course, in this case, our Collection Text module also refers to a non-existing piece of data since it receives an empty collection, therefore it will output an empty text.