Reference: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.
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_IDENT is 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.
Invoking Macros
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 RVALUE.
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.
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.
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.
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.
{{#indexentry optional, macro}} {{#indexentry keyword, optional}}
Optional Parameters
Template:New.3.7.1 Macro parameters can be declared as optional by preceding the
parameter identifier with the keyword optional
; when calling
a macro, you can pass undeclared identifiers to such optional parameters,
or even omit them entirely:
MACRO_DEFINITION: #macro IDENTIFIER ([PARAM_IDENT] [, PARAM_IDENT]... ) TOKENS... #end PARAM_IDENT: [optional] GENERIC_IDENTIFIER MACRO_INVOCATION: MACRO_IDENTIFIER ( [ACTUAL_PARAM] [, ACTUAL_PARAM]... ) ACTUAL_PARAM: [ IDENTIFIER | RVALUE ]
Passing an undeclared identifier to a macro, or omitting a macro parameter entirely, will literally leave the corresponding parameter identifier undeclared as if it wasn't part of the macro's parameter declaration.
Note: If a macro parameter identifier remains
undeclared in this manner, any variables of the same name declared outside the
macro will become visible instead. To reliably test whether an optional
parameter has been supplied, you therefore need to make use of the
local()
pseudo-function, as in the following example.
#macro Foo(P1, optional P2) #ifndef(local(P2)) #local P2 = 0; #end // provide default for P2 ... #end #declare X=1; #declare Y=2; Foo(X,Y); #undef Y; Foo(X,Y); Foo(X,);
Note: The optional parameters mechanism can not be used to return a value via a parameter to a previously undeclared identifier.