Fractal Architect 4 Help Index Fractal Architect icon

Variation Group Chains


Applies to:FA 4

Classic Flame Fractal Algorithm


So each iteration the renderer does this to the pool of point values:

Pick a transform to apply
Picks a transform randomly depending on the transform’s weight.
Apply Pre-Transform Matrix
Apply the pre-transformation matrix operation on the points.

(The triangle in the editor shows the affect of applying the matrix to a simple triangle. When you edit the triangle, you are in fact editing the transformation matrix.)

Apply Variation Calculations
For each variation in the transform’s list of variations, take the transformed point values as input points, apply the variation’s geometric calculations to the input points to get the output points.

These output point values are added together to create the final output point values from the variation list calculations.

Apply Post-Transform Matrix
Take those output points and apply the post transformation matrix to them. These transformed points are then used to mark the histogram in the next step.
Mark Histogram
For each point, mark the histogram if it intersects the output image bounds.

Apophysis Pre, Normal, and Post Variations

Apophysis introduced the concept of Pre, Normal, and Post variations.

Normal Variations
Normal Variations apply a shape calculation to an input point. With Normal variations, the output calculated values are summed together.
Pre Variations
Pre variations take an input point, apply a calculation to it, and replace that input point with the calculated result.
Pos Variations
Post variations take an output point, apply a calculation to it, and replace that output point with the calculated result.

The renderer, in applying variation calculations, will first calculate all the pre-variations, then the normal-variations, then the post-variations.

The output from a pre-variation becomes the input to normal variation. The summed output from the set of normal variations is the input to a post-variation.

So it effectively does this:

pre–1 ==> pre–2 ==> [ normal–1 + normal–2 + … ] ==> post–1 ==> post–2

Note the output from a pre becomes the input to either another pre or the input to all normal variations. Note the summed output from all normal variations becomes the input to the first post variation.

Pre_variations can be chained in series, so for pre_blur ==> pre_flatten

that means output points of pre_blur becomes the input points of the pre_flatten.

Post_variations can be chained in series too.

Normal variations are evaluated in parallel which means they can be evaluated in any order (because addition is cumulative and associative)

Apophysis Pre and Post Variations Were Different

In Apophysis, the variation’s source code and specifically how that code handled the variation’s output values determined if a variation type would have pre, post, or normal behavior. (The community would add pre_ and post_ prefixes to the variation names too).

Thomas Ludwig noted that this behavior could be shifted to the renderer framework, so that any variation type could be used as a pre or post variation. His Chaotica renderer was the first renderer to implement this.

Apophysis Pre, Normal, Post Algorithm

Pre Variations Normal variations Post variations
inX ==> evaluate a new X inX ==> evaluate a new X outX ==> evaluate a new X
assign that new X to inX add that X to outX assign that new X to outX

Pre_variations can be chained in series, so for pre_blur ==> pre_flatten that means input of one becomes the input of the next. Post_variations can be chained in series too. Normal variations are evaluated in parallel which means they can be evaluated in any order (because addition is cumulative and associative)

So: the renderer does this for a specific transform: evaluates pre_variations in series, then evaluates all normal variations in parallel, then evaluates post_variations in series. The order of evaluation for pre and post variations is determined by the ordering of those variation types in the variation set.

Parallel versus Series - Electrical Circuit Analog

So think of this in another way. Variation calculations are just like electric current in a circuit. The flow (and order of execution) of calculations is like electric current in a circuit:

Pre and post variations are applied in series. The output from these variations become the input to the next step in the chain.

Normal variation calculations are applied in parallel. Summing the calculated results together is a parallel operation.

Variation Group Chains

Fractal Architect extends this Apophysis innovation even further.

Think of the set of normal variations as a group, with the group’s output being the summation of the outputs from each variation in the group. These groups are called variation groups.

As with pre and post variations, the groups themselves can be linked in series. The output from one group becomes the input to the next group. The linked groups are called a Variation Group Chain

So it effectively does this (showing 3 variation groups in a chain here):

[ normal–1 + normal–2 + … ] ==> [ normal–1 + normal–2 + … ] ==> [ normal–1 + normal–2 + … ]      OR
group–1 ==> group–2 ==> group–3

So with Fractal Architect, a flame fractal transform, no longer has a set of variations. It has instead a variation group chain, with each variation group being a set of variations. The case of a chain with just a single variation group, has the exact same rendering behavior as with Flam3/Apophysis.

Think of the Fractal Architect Variation Group Chain architecture as being a more general case. From the exact same group of variations as before, you get some completely new image creation capabilities.

If you open a normal Flam3 style fractal, you get a single Variation Group per transform. It can use pre and post variations that have been defined as part of the variation set. One limitation of this older architecture, is that you only get pre and post behavior from special variation types that are included in the variation set.

Why allow multiple Variation Groups?

In the renderer, the evaluated output from a Variation Group becomes the input to the next Variation Group. So variation groups are evaluated in series. The order of evaluation inside a single variation group is the same as the old architecture.

So, that means Group#1 acts like a big Pre variation in affect to Group#2. Group#3 acts like a big Post variation in affect to Group#2. (Order of evaluation is: Group#1, then Group#2, then Group#3)

If we have Group #1 with a single transform instance julian#1 and Group#2 with a single transform instance linear#1, this has the same renderer output as we would get with a single Group having: pre_julian#1 and linear#1.

But there is no pre_julian variation type !! So using chained Variation groups, allows us to use normal variation types as pre/post variation types !

If you want traditional Flam3/Apophysis rendering behavior, just use a single variation group in each transform’s variation group chain. You will get identical renders with those traditional renderers.

Flame Fractal Algorithm Revisited

So the step Apply Variation Calculations from above now becomes:

Apply Variation Calculations
Starting with the first variation group, take these transformed point values as input points, then for each variation in the group, apply the variation’s geometric calculations to the input point to get an output point.

These output point values are added together to create the Group’s output point values

a) If there is a second variation group, take the first group’s output point values and use them as the input point values for the second variation group; calculate the 2nd group’s output point values.

b) If there are more groups in the chain, repeat the behavior of step a).

Take the last variation group’s output points and apply the post transformation matrix to them.

Pre and Post Variations Are Now Redundant

With the Variation Group Chain architecture, the need for specialized pre and post variation types is removed. Fractal Architect still fully supports traditional pre and post variation types. Older fractals that use them, will still render correctly.

With variation groups, the group ordering now determines pre and post behavior. The output from one group becomes the input to the next group.

Example

Lets look at a set of traditional variations: - polar2 - pre_polar2 - post_polar2

So for a Apophysis transform with 2 variations in it: pre_polar2 and julian; Fractal Architect gives you 2 ways to get the same rendering behavior.

A) Single variation group with both pre_polar2 and julian.

[ pre_polar2 ==> julian ]

or

B) Two variation groups in a chain, each group has one variation

[ polar2 ] ==> [ julian ]

Note: we use polar2 – not pre_polar2 !!

Variation Instancing


Another change in Fractal Architect is that you can now have multiple instances of the same variation type in the same variation group. The app now appends the instance number everywhere you used to see

Triangle Editor Instancing

The Triangle Editor shows the Variation Group Chain and for the selected group it shows on top, the variations that have been chosen. The purple variation names are not instances, but are just a way to show the variation names which are available.

When you change a variation’s weight to non-zero, it becomes an instance. When you set a non-zero weight to zero, the variation instance is removed.

The bold variation names just show you as a reminder that that variation type has variation parameters for you to set as well.

Variation Instancing
Variation Instancing

Variation parameters are instanced as well. Variation Parameter Instancing

Info Example Showing Instancing

For the same fractal, here is a Fractal Info snippet showing a normal variation group and the same instanced variations.

Xform #2    weight: 0.1415  colorIndex: 0.9352  color_speed: 0.7964  opacity: 100% var_color: 1  rotates: YES
Variation Group Normal ========
    pre_blur #1: 0.0019  
    julia3Dz #1: 1.5531  power: 1  
    julia3Dz #2: 4  power: 3  
    post_flatten #1: 0.6  

    Pre:  1.0000000000  0.0000000000  0.0000000000
          0.0000000000  1.0000000000  0.0000000000
    Post: 1.0000000000  0.0000000000  0.0000000000
          0.0000000000  1.0000000000  0.0000000000


Effective Use of Instancing

Effective

Instancing imposes a run-time overhead of doing variation calculations for each instance. So for the example shown, the julia3Dz calculations will be done twice, once per instance. In this example, the variation weights and the power parameter for the instances were different. This is effective use of instancing. The different power parameters between the instances cannot be simplified.

On the other hand, if both instances had the exact same parameter values, then the two instances could be merged into one instance (with its weight being the summation of the original instances’ weights).

Ineffective

Now lets use the example of two linear variation instances: linear#1 and linear#2.

Linear#1 has weight of 1 and linear#2 has weight 2.

Since the linear variation type has no variation parameters, you can get the same output with a single linear instance (using linear#1 with weight of 3). You can add the instances’ variation weights together and just use one instance with the summed weight.

That is more efficient as only one variation calculation is done instead of two.

Lua Scripting

Each xforms table has sub-tables holding all of the xform’s variation groups.

preVarGroups
Contains the list of Pre variation groups.
variations
This is the Normal variation group.
postVarGroups
Contains the list of Post variation groups.

Example Variations Snippet

This snippet inserts a julia3D variation instance into

xform = flame.xforms[2]
table.insert(xform.variations, { name="julia3D",      weight=0.7, julia3D_power=-2 })

Example Lua Script

-- Randomly (33% probability) adds a Cpow variation to the Pre Variation Group for all xforms in the fractal
for i,v in ipairs(flame.xforms) do    
    if math.random() < 0.3 then
        -- make sure there is a Pre Variation group to put the Cpow variation into
        if #v.preVarGroups == 0 then
            table.insert(v.preVarGroups, {})
        end
        
        table.insert(v.preVarGroups[1], { name="cpow", weight=math.random(),
                                            cpow_i = 0.5 + math.random() * 1.5, 
                                            cpow_power = math.random(1, 5),
                                            cpow_r = 0.1 + math.random() * 2.9});
    end
end