
#declaring functions
It's possible to #declare a function for later use in an isosurface,
but the x, y and z values used in the declaration are formal parameters
and it's possible to substitute different actual parameters when
the function is invoked.
#declare S = function {x*x + y*y + z*z  1}
isosurface {
function { S(x,y,z) }
accuracy 0.001
contained_by{sphere{0,R}}
pigment {rgb .9}
}
In this case, we use the same actual parameters as the formal parameters,
and the result is a sphere, exactly as if we had said
isosurface {
function {x*x + y*y + z*z  1}
...
}


Scale
But we don't have to use the same parameters when we invoke the function.
We could use function { S(x/2,y,z) } instead of
function { S(x,y,z) }.
This has the effect of performing a substitution of x/2 in the equation
wherever the variable x occurred, giving us a surface equivalent to
isosurface {
function {(x/2)*(x/2) + y*y + z*z  1}
...
}
We could have written that in the first place, or we could have applied
a conventional scale <2,1,1> transformation
to the whole thing. Note: Applying scale would also scale the
contained_by surface.
Note that to scale the x dimension by a factor of 2, we substituted x/2.
This is a common feature of substitutions  things change in an inverse
way to the change made to the variable. If the variable is halved then the scale doubles.


function { S(x, 0, z) }
Substituting 0 for y causes the
sphere to degenerate into a cylinder.
You might consider this to be equivalent of an infinite scaling in the y direction.


Translation
function { S(x/2, y0.5, z) }
Substituting y0.5 for y causes the
surface to be translated +0.5 in the y direction. We could have
achieved this effect by translate <0, 0.5, 0>.


Shear
It's a bit more dificult to produce a particular shear transformation,
because the values that we need to use in the variable substitution need to be the inverse of
those normally used to generate the shear.
We could calculate the inverse of the transformation matrix by hand, but it is possible to get POVRay to do it for us.
We first create a shear transformation, then declare a transformation function using its inverse.
#include "transforms.inc"
#declare TR=Shear_Trans(<0.5,0.5,0.5>,<1.1,0,0.3>,<0.3,0.5,0>)
#declare TRFI = function {transform {TR inverse}}
Now we can apply that transformation function to the unit vectors which gives us the values we need for the substitution.
However, we can't use vectors from inside an isosurface function,
so we have to extract the x, y and z values of each of those vectors outside the isosurface.
#declare A=TRFI(1,0,0);
#declare B=TRFI(0,1,0);
#declare C=TRFI(0,0,1);
#declare Ax=A.x;#declare Bx=B.x;#declare Cx=C.x;
#declare Ay=A.y;#declare By=B.y;#declare Cy=C.y;
#declare Az=A.z;#declare Bz=B.z;#declare Cz=C.z;
We can now use these scalar values in the variable substitution.
function { F(Ax*x+Bx*y+Cx*z, Ay*x+By*y+Cy*z, Az*x+Bz*y+Cz*z) }
The image on the left shows two boxes. One of them is sheared by variable substitution
and the other is sheared with the equivalent conventional shear transform.
The same method can be used for combinations of shear, rotation and scale operations.
It doesn't seem to work for transformations that involve translation, but we already know how to do that.


Flip
#declare P = function {x*x + y + z*z  1}
isosurface {
function { P(y,x,z) }
...
Substituting y for x and substituting x for y causes this paraboloid
to be flipped round to face in the x direction instead of the +y direction.


Nonlinear scale
#declare P = function {x*x + y*y + z*z  1}
isosurface {
function { P(x,y*(1.05y/5),z) }
...
A nonlinear stretch has turned this sphere into something like a hen's egg.
The sphere is stretched more as y becomes larger, and compressed more as y
becomes more negative.


Surface of revolution
This is a particularly interesting variable substitution.
It generates a surface of revolution from the positive part of an equation
of two variables.
In this case the function F describes the sin wave "y = sin(x) + 4", the "+4" is
there just to make the value of y always positive.
#declare F = function {ysin(x)4}
isosurface {
function { F(x, sqrt(y*y+z*z), z) }
...


You can create a surface of revolution from the equation of any 2d curve in the same way.
A particular instance of a surface of revolution is the torus.
The parameters r1 and r2 are the major and minor radii.
The original function {x*x + y*y  r2*r2} creates a 2d circle
of radius r2, and the substitution shifts that circle a distance r1 along the x axis,
then creates a surface of revolution from it.
#declare F = function {x*x + y*y  r2*r2}
isosurface {
function { F(sqrt(x*x+z*z)r1, y, z) }
...


By transforming from cartesian to cylindrical polar coordinates, it's possible to bend
any isosurface into a circle around the origin.
The cylindical polar coordinates are made available through the f_th() and f_r() functions,
so you don't have to work them out for yourself.
#declare F=function { f_helix1
(x, z, y, Strands, Turns, R1, R2, 0.6, 2, 0) }
isosurface {
function{F(f_r(x,y,z)R3, y, f_th(x,y,z))}
...


Similarly, it's possible to wrap a sheetlike isosurface around a sphere by
transforming from cartesian to spherical polar coordinates.
The spherical polar coordinates are made available through the f_th(), f_r() and f_ph() functions,
so you don't have to work them out for yourself.
The image on the left is created from a f_mesh1() function which has been converted to polar coordinates.
The threads that previously lay in the x and z directions now lie in the longitude and latitude
directions of the sphere. What was previously the height in the y direction is now altitude in the
radial direction, with the "1" lifting the whole thing radially away from the origin.
#declare F=function {f_mesh1
(x, y, z, 0.15, 0.15, 1, 0.02, 1)  0.03}
isosurface {
function { F(f_ph(x,y,z), f_r(x,y,z)1, f_th(x,y,z)) }
...


It's possible to use a single isosurface to produce multiple copies of a shape by using the mod
operator in a substitution.
Using mod(x,2) will cause the shape to be repeated in the x direction every 2 units.
If your original isosurface created a shape centred at the origin, then there's a problem with the
way that it chooses which bits to repeat. The left half of the object gets repeated in one direction
and the right half of the object gets repeated in the other direction.
To fix this, we'd like to substitute mod(x,2)+1 where x is negative and mod(x,2)1
where x is positive. To fix both directions at once, for a symmetrical object, we can use abs(x)
like this mod(abs(x),2)1.
To change the length of the repeat unit we can do this mod(abs(x),Step)Step/2.
We can repeat things in more than one direction, making a sheet or filling a volume with repeated units by
performing similar substitutions in the x, y and z directions.
This trick can be extremely useful in situations where awkward CSG operations cause POV to apply very inefficient
bounding. If we had created thousands of separate badly bounded objects, then POV would have to perform
thousands of intersection tests on each ray. If we use this trick to use a single isosurface to generate
thousands of objects, then POV only has to perform a single intersection test on each ray. That intersection
test may well be somewhat slower than that for a non repeating surface, but it's likely to be much
faster than performing a thousand such tests.
You need to be particularly careful with your max_gradient setting for this trick.
Slight changes to the step size sometimes require large changes in max_gradient.
#declare F = function {f_torus(y,x,z,0.8,0.19)}
#declare Step = 0.75;
isosurface {
function { F ( mod(abs(x),Step)Step/2, y, z) }


Thickening
If we have a function F(x,y,z) we can turn it into two parallel
surfaces by using abs(F(x,y,z))C where
C is some small value. The original function should be one that
works with zero threshold. The two resulting surfaces are what you would
get by rendering the original function with threshold +C and C, but
combined into a single image. The space between the two surfaces becomes
the "inside" of the object. In this way we can construct things like glasses
and cups that have walls of nonzero thickness.
#declare F = function {y
+f_noise3d(x*2, 0, z*2)
}
isosurface {
function { abs(F(x,y,z))0.1 }
...

