2.2.2.8 User Defined Macros
POV-Ray 3.1 introduced user defined macros with parameters. This feature, along with the ability to declare #local
variables, turned the POV-Ray Language into a fully functional programming language. Consequently, it is now possible
to write scene generation tools in POV-Ray's own language that previously required external utilities.
2.2.2.8.1 The #macro Directive
The syntax for declaring a macro is:
MACRO_DEFINITION:
#macro IDENTIFIER ([PARAM_IDENT] [, PARAM_IDENT]... ) TOKENS... #end
Where IDENTIFIER is the name of the macro and PARAM_IDENTs are a list of zero or more formal
parameter identifiers separated by commas and enclosed by parentheses. The parentheses are required even if no
parameters are specified.
The TOKENS are any number of POV-Ray keyword, identifiers, or punctuation marks which are the body
of the macro. The body of the macro may contain almost any POV-Ray syntax items you desire. It is terminated by the #end
directive.
Note: any conditional directives such as #if ...#end ,
#while ...#end , etc. must be fully nested inside or outside the macro so that the corresponding #end
directives pair-up properly.
A macro must be declared before it is invoked. All macro names are global in scope and permanent in duration. You
may redefine a macro by another #macro directive with the same name. The previous definition is lost.
Macro names respond to #ifdef , #ifndef , and #undef directives. See "The
#ifdef and #ifndef Directives" and "Destroying Identifiers with
#undef".
You invoke the macro by specifying the macro name followed by a list of zero or more actual parameters enclosed in
parentheses and separated by commas. The number of actual parameters must match the number of formal parameters in the
definition. The parentheses are required even if no parameters are specified. The syntax is:
MACRO_INVOCATION:
MACRO_IDENTIFIER ( [ACTUAL_PARAM] [, ACTUAL_PARAM]... )
ACTUAL_PARAM:
IDENTIFIER | RVALUE
An RVALUE is any value that can legally appear to the right of an equals sign in a #declare
or #local declaration. See "Declaring identifiers"
for information on RVALUEs. When the macro is invoked, a new local symbol table is created. The actual
parameters are assigned to formal parameter identifiers as local, temporary variables. POV-Ray jumps to the body of
the macro and continues parsing until the matching #end directive is reached. There, the local variables
created by the parameters are destroyed as well as any local identifiers expressly created in the body of the macro.
It then resumes parsing at the point where the macro was invoked. It is as though the body of the macro was cut and
pasted into the scene at the point where the macro was invoked.
Note: it is possible to invoke a macro that was declared in another file. This is
quite normal and in fact is how many "plug-ins" work (such as the popular Lens Flare macro). However, be
aware that calling a macro that was declared in a file different from the one that it is being called from involves
more overhead than calling one in the same file.
This is because POV-Ray does not tokenize and store its language. Calling a macro in another file therefore
requires that the other file be opened and closed for each call. Normally, this overhead is inconsequential; however,
if you are calling the macro many thousands of times, it can cause significant delays. A future version of the POV-Ray
language will remove this problem.
Here is a simple macro that creates a window frame object when you specify the inner and outer dimensions.
#macro Make_Frame(OuterWidth,OuterHeight,InnerWidth,
InnerHeight,Depth)
#local Horz = (OuterHeight-InnerHeight)/2;
#local Vert = (OuterWidth-InnerWidth)/2;
difference {
box{
<0,0,0>,<OuterWidth,OuterHeight,Depth>
}
box{
<Vert,Horz,-0.1>,
<OuterWidth-Vert,OuterHeight-Horz,Depth+0.1>
}
}
#end
Make_Frame(8,10,7,9,1) //invoke the macro
In this example, the macro has five float parameters. The actual parameters (the values 8, 10, 7, 9, and 1) are
assigned to the five identifiers in the #macro formal parameter list. It is as though you had used the
following five lines of code.
#local OuterWidth = 8;
#local OuterHeight = 10;
#local InnerWidth, = 7;
#local InnerHeight = 9;
#local Depth = 1;
These five identifiers are stored in the same symbol table as any other local identifier such as Horz
or Vert in this example. The parameters and local variables are all destroyed when the #end
statement is reached. See "Identifier Name Collisions" for a
detailed discussion of how local identifiers, parameters, and global identifiers work when a local identifier has the
same name as a previously declared identifier.
2.2.2.8.3 Are POV-Ray Macros a Function or a Macro?
POV-Ray macros are a strange mix of macros and functions. In traditional computer programming languages, a macro
works entirely by token substitution. The body of the routine is inserted into the invocation point by simply copying
the tokens and parsing them as if they had been cut and pasted in place. Such cut-and-paste substitution is often
called macro substitution because it is what macros are all about. In this respect, POV-Ray macros are
exactly like traditional macros in that they use macro substitution for the body of the macro. However traditional
macros also use this cut-and-paste substitution strategy for parameters but POV-Ray does not.
Suppose you have a macro in the C programming language Typical_Cmac(Param) and you invoke it as Typical_Cmac(else
A=B) . Anywhere that Param appears in the macro body, the four tokens else , A ,
= , and B are substituted into the program code using a cut-and-paste operation. No type
checking is performed because anything is legal. The ability to pass an arbitrary group of tokens via a macro
parameter is a powerful (and sadly often abused) feature of traditional macros.
After careful deliberation, we have decided against this type of parameters for our macros. The reason is that
POV-Ray uses commas more frequently in its syntax than do most programming languages. Suppose you create a macro that
is designed to operate on one vector and two floats. It might be defined OurMac(V,F1,F2) . If you allow
arbitrary strings of tokens and invoke a macro such as OurMac(<1,2,3>,4,5) then it is impossible to
tell if this is a vector and two floats or if its 5 parameters with the two tokens < and 1
as the first parameter. If we design the macro to accept 5 parameters then we cannot invoke it like this... OurMac(MyVector,4,5) .
Function parameters in traditional programming languages do not use token substitution to pass values. They create
temporary, local variables to store parameters that are either constant values or identifier references which are in
effect a pointer to a variable. POV-Ray macros use this function-like system for passing parameters to its macros. In
our example OurMac(<1,2,3>,4,5) , POV-Ray sees the < and knows it must be the start
of a vector. It parses the whole vector expression and assigns it to the first parameter exactly as though you had
used the statement #local V=<1,2,3>; .
Although we say that POV-Ray parameters are more like traditional function parameters than macro parameters, there
still is one difference. Most languages require you to declare the type of each parameter in the definition before you
use it but POV-Ray does not. This should be no surprise because most languages require you to declare the type of any
identifier before you use it but POV-Ray does not. This means that if you pass the wrong type value in a POV-Ray macro
parameter, it may not generate an error until you reference the identifier in the macro body. No type checking is
performed as the parameter is passed. So in this very limited respect, POV-Ray parameters are somewhat macro-like but
are mostly function-like.
2.2.2.8.4 Returning a Value Like a Function
POV-Ray macros have a variety of uses. Like most macros, they provide a parameterized way to insert arbitrary code
into a scene file. However most POV-Ray macros will be used like functions or procedures in a traditional programming
language. Macros are designed to fill all of these roles.
When the body of a macro consists of statements that create an entire item such as an object, texture, etc. then
the macro acts like a function which returns a single value. The Make_Frame macro example in the section
"Invoking Macros" above is such a macro which returns a value
that is an object. Here are some examples of how you might invoke it.
union { //make a union of two objects
object{ Make_Frame(8,10,7,9,1) translate 20*x}
object{ Make_Frame(8,10,7,9,1) translate -20*x}
}
#declare BigFrame = object{ Make_Frame(8,10,7,9,1)}
#declare SmallFrame = object{ Make_Frame(5,4,4,3,0.5)}
Because no type checking is performed on parameters and because the expression syntax for floats, vectors, and
colors is identical, you can create clever macros which work on all three. See the sample scene MACRO3.POV
which includes this macro to interpolate values.
// Define the macro. Parameters are:
// T: Middle value of time
// T1: Initial time
// T2: Final time
// P1: Initial position (may be float, vector or color)
// P2: Final position (may be float, vector or color)
// Result is a value between P1 and P2 in the same proportion
// as T is between T1 and T2.
#macro Interpolate(T,T1,T2,P1,P2)
(P1+(T1+T/(T2-T1))*(P2-P1))
#end
You might invoke it with P1 and P2 as floats, vectors, or colors as follows.
sphere{
Interpolate(I,0,15,<2,3,4>,<9,8,7>), //center location is vector
Interpolate(I,0,15,3.0,5.5) //radius is float
pigment {
color Interpolate(I,0,15,rgb<1,1,0>,rgb<0,1,1>)
}
}
As the float value I varies from 0 to 15, the location, radius, and color of the sphere vary
accordingly.
There is a danger in using macros as functions. In a traditional programming language function, the result to be
returned is actually assigned to a temporary variable and the invoking code treats it as a variable of a given type.
However macro substitution may result in invalid or undesired syntax. The definition of the macro Interpolate
above has an outermost set of parentheses. If those parentheses are omitted, it will not matter in the examples above,
but what if you do this...
#declare Value = Interpolate(I,0,15,3.0,5.5)*15;
The end result is as if you had done...
#declare Value = P1+(T1+T/(T2-T1))*(P2-P1) * 15;
which is syntactically legal but not mathematically correct because the P1 term is not multiplied. The
parentheses in the original example solves this problem. The end result is as if you had done...
#declare Value = (P1+(T1+T/(T2-T1))*(P2-P1)) * 15;
which is correct.
2.2.2.8.5 Returning Values Via Parameters
Sometimes it is necessary to have a macro return more than one value or you may simply prefer to return a value via
a parameter as is typical in traditional programming language procedures. POV-Ray macros are capable of returning
values this way. The syntax for POV-Ray macro parameters says that the actual parameter may be an IDENTIFIER
or an RVALUE. Values may only be returned via a parameter if the parameter is an IDENTIFIER.
Parameters that are RVALUES are constant values that cannot return information. An RVALUE is
anything that legally may appear to the right of an equals sign in a #declare or #local
directive. For example consider the following trivial macro which rotates an object about the x-axis.
#macro Turn_Me(Stuff,Degrees)
#declare Stuff = object{Stuff rotate x*Degrees}
#end
This attempts to re-declare the identifier Stuff as the rotated version of the object. However the
macro might be invoked with Turn_Me(box{0,1},30) which uses a box object as an RVALUE
parameter. This will not work because the box is not an identifier. You can however do this
#declare MyObject=box{0,1}
Turn_Me(MyObject,30)
The identifier MyObject now contains the rotated box.
See "Identifier Name Collisions" for a detailed discussion
of how local identifiers, parameters, and global identifiers work when a local identifier has the same name as a
previously declared identifier.
While it is obvious that MyObject is an identifier and box{0,1} is not, it should be
noted that Turn_Me(object{MyObject},30) will not work because object{MyObject} is
considered an object statement and is not a pure identifier. This mistake is more likely to be made with
float identifiers versus float expressions. Consider these examples.
#declare Value=5.0;
MyMacro(Value) //MyMacro can change the value of Value but...
MyMacro(+Value) //This version and the rest are not lone
MyMacro(Value+0.0) // identifiers. They are float expressions
MyMacro(Value*1.0) // which cannot be changed.
Although all four invocations of MyMacro are passed the value 5.0, only the first may modify the value
of the identifier.
|