Summary

Finding a path across clouds of points – a random coil

Demo-file: Demo2.pov

First let's take a look at the positions, which are used to define the random coil. The positions are generated by a macro which distributes positions in a regular x-y-z pattern and adds a defined amount of random variation to this pattern (red spheres, below left). Because we want to confine our coil to a certain volume, only positions within a predefined volume (here a yellow sphere, below center) are selected (below right). These positions are stored in an array (which is called "Positions" in our example).

random positions random positions and sphere random positions within a sphere

The form of the coil generated from these positions depends on the order, the positions are extracted from the array. When the positions are extracted from the array in the same order as the array had been defined, a very ordered coil is produced (below left). When attempting to extract the positions in a random manner from the array, there is the possibility to either automatically delete the extracted position from the array (below right), or to keep it within the array. The latter approach allows the possibility that a given position makes part of the sphere_sweep several times (below center), while other positions from the array are not included in the sweep.

ordered coil random coil1 random coil2

We start the discussion of the code used in this chapter with the macro used to generate the random positions.

#macro xyzDistributionInsideArray (Object, Start, Amountx, Amounty, Amountz, Distancex, Distancey, Distancez, Variance)
//This macro generates positions in an xyz-pattern of a given variance within a given object
//and creates the array "Positions" containing the respective coordinates.
//The macro has the following parameters: The object containing the positions, the start of the xyz-pattern,
//the amount of positions and the distance between positions for each coordinate, the variance.
//Care should be taken to choose appropriate parameters, otherwise there may be no positions within the object,
//or calculation times may be longer than necessary.


#declare Positions = array [Amountx * Amounty * Amountz];//The array is initiated
#declare Sum = 0;//The variable storing the size of the array
#local xKoord = Start.x; //Three variables storing x-, y-, and z-positions are set to the start position.
#local yKoord = Start.y;
#local zKoord = Start.z;

//Three nested loops are generating the xyz-pattern.

#local ticker3 = 0;
#while (ticker3 < Amountx)
#local xKoord = xKoord + Distancex;
#local yKoord = Start.y;
#local ticker2 = 0;
#while (ticker2 < Amounty)
#local yKoord = yKoord + Distancey;
#local zKoord = Start.z;
#local ticker = 0;
#while (ticker < Amountz)
#local zKoord = zKoord + Distancez;

//Three random numbers are generated from pseudo-random streams defined before invoking the macro.

#local var1 = rand(chance1);
#local var2 = rand(chance2);
#local var3 = rand(chance3);

//Individual positions are defined...

#local P1 = <xKoord + Variance * (var1 - 0.5), yKoord + Variance * (var2 - 0.5), zKoord + Variance * (var3 - 0.5)>;
#if (inside (Object, P1) = 1)

//...and stored inside the array, in case they are situated inside the object.

#declare Positions[Sum] = P1;
#declare Sum = Sum + 1;
#else
#end

//End of the loops and of the macro.

#local ticker = ticker + 1;
#end
#local ticker2 = ticker2 + 1;
#end
#local ticker3 = ticker3 + 1;
#end
#end

Before invoking this macro we have to initiate three pseudo-random streams and define the containing object.

#declare chance1 = seed (13);
#declare chance2 = seed (15);
#declare chance3 = seed (18);


#declare Container = sphere { <0, 0, 0>, 3 };

xyzDistributionInsideArray (Container, <-3, -3, -3>, 10, 10, 10, 1, 1, 1, 0.6)

In this example, the positions will be defined within the object "Container". They will have a mean distance of 1 from each other in each direction and the variability is set to 0.6.

We could just use the macro from the previous chapter to extract positions from the array "Positions" formed by the macro above.

DrawSphereSweep (Positions, 0.2, Sum)

This macro, however, extracts the positions in exactly the same order from the array as they had been deposited in the array. We will obtain a very ordered coil when connecting the positions in this order (see figure above).

Replacing the line

#declare P1 = Positions[ticker];

from this macro with the line

#declare P1 = Positions[floor(rand(chance1)*0.9999999*Sum)];

results in a pseudo-random extraction of positions from the array, i.e., in a pretty random coil. (Solution taken from "arrays.inc"). The problem with this easy solution is, however, that the positions used for the sphere_sweep are not eliminated from the array. This means the same position can be selected several times to define the sphere_sweep, while other positions won't be used at all.
For the elimination of selected elements from a given array a new macro had to be written. This macro defines a new array, one element smaller than the original one and transfers every element, except the randomly chosen element to this macro.

#macro RandomArrayElement (ArrayName, ArraySize)
//This macro randomly chooses an element from an array
//(which is stored as P1) and eliminates the element from the array.
//Because the array is changed during this operation, care should be taken to store the original array.
//The parameter ArraySize refers to the actual number of elements in the array.
//It is provided by the macro in the form of the variable "Size".
//When running the macro within a loop, this variable Size should be provided to the array as the parameter ArraySize.
//The macro can only work for arrays containing more than 1 element (caution, when starting a loop).


#local ArrayPosition = floor(rand(chance1)*0.9999999*(ArraySize));
#declare P1 = ArrayName[ArrayPosition]; //an element of the array is randomly chosen.
#local PositionsNew = array[ArraySize-1];//The new array is automatically one element shorter than the old
//array. Because an array is not allowed to become 0 size, this operation cannot be performed with size 1 arrays.


//The following loop transfers all elements (except the element selected) from the old array to the new array.

#local ticker2 = 0;
#local ticker3 = 0;
#while (ticker2 < ArraySize)
#if (ticker2 = ArrayPosition)
#local ticker3 = ticker3 -1;
#else
#local P2 = ArrayName[ticker2];
#local PositionsNew [ticker3] = P2;
#end
#local ticker2 = ticker2 + 1;
#local ticker3 = ticker3 + 1;
#end

//End of the loop. Now the new array defined in this loop becomes the input (and output) array.

#declare ArrayName = PositionsNew;
#declare Size = dimension_size(PositionsNew,1);
#end

The loop defining the respective sphere_sweep then has the following form:

#declare chance1 = seed(2);

//Because the original array and size are changed during the following operation,
//care should be taken to create working variables and store the original ones.


#declare Size = Sum;
#declare PositionsWork = Positions;

//Here the sphere_sweep is initiated.

sphere_sweep {
cubic_spline
Sum-1, // has to be Sum-1, because otherwise an array of size 0 is generated.

//The loop for extracting the positions from the array

#declare ticker = 0;
#while (ticker < Sum-1)
RandomArrayElement (PositionsWork, Size)
P1, 0.1
#declare ticker = ticker + 1;
#end

//End of the sphere_sweep

pigment {
color rgb <0, 0, 1>

}
}

Summary