Previous page Next page

User Defined Macros

New in POV-Ray 3.1 are user defined macros with parameters. This new feature, along with the ability to declare #local variables, turns the POV-Ray Language into a fully functional programming language. It should now be possible to write scene generation utilities 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_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 my the #end directive. Note however that 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. Also you may not nest macro declarations.

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 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.

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,Hoiz,-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. This is especially true because the POV-Ray language has no user-defined functions or procedures. 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. Note 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 won't 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 verses 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.

Previous page Next page