HowTo:Use conditional structures

From POV-Wiki
Jump to navigation Jump to search

Conditional constructs in POV-Ray are scripting features which perform different computations or actions depending on whether a condition evaluates to true or false.


#if

The #if statement can only evaluate floating point expressions. There is no boolean logic using and, or or not. The equivalent must be achieved using arithmetic.

Here's an example of and where a rotation (for a texture, say) depends on both the row and column. Enclosing both the conditions in brackets isolates them and each produces either 0 or 1. Summing them will give 0, 1 or 2. The rotation will occur when both are true, producing 2.

#local fRotateY = 0;
// If Row = 1 and Col = 4
#if ((uiRow = 1) + (uiCol = 4) = 2)
    // Rotates only row 1, column 4.
    #local fRotateY = 45;
#end

And here's an example of or where the rotation depends on the column having either of two values. Again, enclosing both the conditions in brackets isolates them and each produces either a 0 or a 1. Summing them will give 0, 1 or 2. The rotation will occur when either or both are true and they produce 1 or 2. Note that the > 0 is optional and just a matter of style.

#local fRotateY = 0;
// If Row = 1 or Col = 6
#if ((uiRow = 1) + (uiCol = 6) > 0)
    // Rotates all of row 1 and all of column 6
    #local fRotateY = 45;
#end

And here's a truly ugly looking case which uses both. This is when you start wishing that they'd introduce a boolean type. ;-)

#local fRotateY = 0;
// If (Row = 1 and Col = 4) or Col = 6
#if (((uiRow = 1) + (uiCol = 4) = 2) + (uiCol = 6) > 0)
    // Rotates row 1, column 4 and all of column 6
    #local fRotateY = 45;
#end




The #switch, #case, #range and #break Directives

The #switch directive provides another way of specifying conditional expressions but allows you to handle a succession of conditions as defined by a #case clause or a #range clause. This directive can seem a little daunting compared to using one or more #if directives, but there are some powerful ways of using it. Like the #if directive it uses float values rather than boolean, so two values whose difference is less than 1e-10 are considered equal.

This trivial example illustrates a number of basic points about the #switch directive.

  • If a #case or #range clause is not a match it skips to the next one, ignoring everything else in between.
  • If a #break is encountered within a matching #case or #range clause the #switch directive stops processing further lines, causing an immediate jump to the #end without processing subsequent sections, even if a subsequent condition would also have been satisfied.
  • When processing a matching #case or #range clause, if a #break is not encountered it continues through successive #case statements whether they match or not.
#declare MyVariable = 5.2;
#debug concat("MyVariable: ",str(MyVariable,3,3),"\n")
#switch (MyVariable)
  #case (0)
    #debug "As close to nothing as makes a tiny difference.\n"
  #break 
  #case (1)
  #case (2)
  #case (3)
  #case (5)
  #case (7)
    #debug "A Prime number between 1 and 10 (actually within 1e-10 of a prime).\n"
  #break 
  #range (0,10)
    #debug "A number between 1 and 10 that is not within 1e-10 of a Prime number\n"
  #break
  #range (10,100)
    #debug "A number between 10 and 100 (actually 10+(1e-10) to 100+(1e-10)).\n"
  #break
  #else
    #debug "Everything else.\n"
    #debug "ie. Less than zero or greater than 100.\n"
#end

If the parameter to a #case clause matches the parameter to the #switch clause then everything up to the next #break, #else or #end clause is evaluated. Similarly, if the parameter to the #switch clause is between the minimum and maximum values of a #range clause then everything up to the next #break, #else or #end clause is evaluated. To explore the sensitivity to the accuracy of the floating point number comparison you can set a value using 'e' as follows:

#declare MyVariable = 5+(0.5e-10);
  or
#declare MyVariable = 5+(1.5e-10);

Using #switch with text values

The #switch, #case and #range clauses accept parameters that evaluate to float numbers. This includes the strcmp function which compares two strings and returns a zero if two strings match. By turning the #switch clause on its head a little, you can therefore use it with strings. Using the #switch directive with text can be particularly handy when writing macros that can generate an object in a large number of differently styles, because you can give each style a name and set parameters based on the name of the style specified.


Example of using #switch with text:

#declare MyVariable = "Ostrich";
#switch (0)
  #case (strcmp(strupr(MyVariable),"DOG"))
    #debug "Dog, DOG, or dog.\n"
    // Settings to be used for a dog
  #break 
  #case (strcmp(strupr(MyVariable),"CAT"))
    #debug "Cat, CAT, or cat.\n"
    // Settings to be used for a cat
  #break 
  #case (strcmp(strupr(MyVariable),"OSTRICH"))
    #debug "Ostrich, OSTRICH, or ostrich.\n"
    // Settings to be used for an ostrich
  #break 
  #else
    #debug "Not a dog, cat or ostrich.\n"
    // Stuff to do if an unexpected creature was encountered.
#end

Note that the strupr function is used to remove case sensitivity.

The following example illustrates the use of multiple #case clauses for a set of associated conditions to work out how many legs an animal has:

#declare Animal   = "Ostrich";
#switch (0)
  #case (strcmp(strupr(Animal),"DOG"))
  #case (strcmp(strupr(Animal),"CAT"))
  #case (strcmp(strupr(Animal),"RHINOCEROS"))
  #case (strcmp(strupr(Animal),"TABLE"))
    #declare LegCount = 4;
  #break 
  #case (strcmp(strupr(Animal),"OSTRICH"))
  #case (strcmp(strupr(Animal),"DUCK"))
    #declare LegCount = 2;
  #break 
  #case (strcmp(strupr(Animal),"WORM"))
  #case (strcmp(strupr(Animal),"DOLPHIN"))
    #declare LegCount = 0;
  #break 
  #else
    #declare LegCount = -1; // Unknown
#end

#debug concat("Animal: ",Animal,"\n") 
#if (LegCount<0) #debug "Number of Legs Unknown\n"
#else #debug concat("Number of Legs: ",str(LegCount,3,0),"\n")
#end

You can also test for abbreviations using the following construct:

  #case (strcmp(strupr(Animal),substr("RHINOCEROS",1,strlen(Animal))))

This would match 'Rhino' or 'Rhinoceros', but you clearly need to be a little careful with this as it would also match 'Rhinoc', 'R' and . To set a minimum abbreviation length use the max function:

  #case (strcmp(strupr(Animal),substr("RHINOCEROS",1,max(strlen(Animal),5))))

This would match 'Rhino', 'Rhinoceros' and everything in between, but not 'R', 'Rhin' or ''.