You are here

More advanced features


  • CSG: how to make complex objects

    • union
    • merge
    • difference
    • intersection
    • A problem with CSG
  • Simple Programming: #if ... #end and #while ... #end

    • The #if Statement
    • The #while Statement

CSG: how to make complex objects

Constructive Solid Geometry, or CSG, is an extremely powerful way to make objects that can't easily be represented by the simple objects we've discussed so far (like box, sphere, etc.). There are four basic CSG operations: union, merge, difference, and intersection.

The union statement

union takes a list of objects and "ties" them together so they behave as a single unit. To use union, simply enclose the list of objects with: union { .... }.

Objects linked with union can be altered all at once by changing the properties of the union. For example, let's draw a collection of spheres, like this:

#include "colors.inc"
#include "textures.inc"
#include "finish.inc"

camera {location <1,1,-6> look_at <0,0,0>}
light_source {<0,0,-50> color White}

plane {y, -1 pigment {checker color Red, White}}

#declare SimpleSphere = sphere {<0,0,0>, 0.5 pigment {color Red}}

#declare Cluster = union {
 object {SimpleSphere}
 object {SimpleSphere translate <1,0,1>}
 object {SimpleSphere translate <0,1,2>}
 object {SimpleSphere translate <1,1,3>}
}

object {Cluster}


The cluster of spheres looks like this:

union.jpg

Now, change the last line to:

object {Cluster translate <-2,0,-0.5>}


and see what happens:

union2.jpg

Nice! The whole group of spheres moves on your command.

It's possible to change the color or entire texture of a union. There's only one requirement: to apply a single texture (or color, finish, etc.) to an entire union, make sure the objects in the union don't have one already. If the SimpleSphere object didn't already have a red color, you could change the color of the whole union like this:

#declare SimpleSphere = sphere {<0,0,0>, 0.5}

#declare Cluster = union {
 object {SimpleSphere}
 object {SimpleSphere translate <1,0,1>}
 object {SimpleSphere translate <0,1,2>}
 object {SimpleSphere translate <1,1,3>}
}

object {Cluster pigment {White_Marble} translate <-2,0,-0.5>}


union3.jpg

The merge statement

merge is exactly like union except that portions of two objects that overlap "internally" are deleted. For opaque objects, there is no difference between the two (except a union renders more quickly). However, for transparent objects, there's a big difference. Let's render two pairs of overlapping spheres:

#include "colors.inc"
#include "textures.inc"
#include "finish.inc"

camera {location <0,1,-4> look_at <0,0.4,0>}
light_source {<0,40,-20> color White}

plane {y, -4 pigment {color White} finish {Dull}}

#declare SimpleSphere1 = sphere {<0,0,0>, 0.5 
 pigment {color rgbf <1,1,1,1>}
 finish {Glass_Finish}}
#declare SimpleSphere2 = sphere {<0,0,0>, 0.5 
 pigment {color rgbf <1,0.5,0.5,1>}
 finish {Glass_Finish}}

#declare Pair1 = union {
 object {SimpleSphere1}
 object {SimpleSphere2 translate <0.35,0.,0>}
 }

#declare Pair2 = merge {
 object {SimpleSphere1}
 object {SimpleSphere2 translate <0.35,0,0>}
 }

object {Pair1 translate <-1,0,0>}
object {Pair2 translate <1,0,0>}


Here, we've defined two spheres, the first colorless (rgb 1,1,1), and the second reddish (rgb 1,0.5,0.5). We'll declare one pair to be a union, the other a merge, and move them apart so we can see what's going on:

merge.jpg


On the left is a union, on the right is a merge.

You can see that in the union, the red sphere intrudes into the colorless one, and vice versa, whereas the merge removes these overlaps.

The difference statement

Use the difference statement to cut one object with another, removing the cut portion altogether. The general form of the difference statement is:

difference {
object {Original}
object {Cutting Object 1....}
object {Cutting Object 2....}
...
}


i.e., specify the object to be "cut into" first, and then all the other objects that will affect it. A simple example: a cone sliced by a box rotated at an angle:

#include "colors.inc"
#include "textures.inc"
#include "finish.inc"

camera {location <2,2,-6> look_at <0,0,0>}
light_source {<0,40,-20> color White}

plane {y, 0 pigment {color White} finish {Dull}}

#declare SimpleCone = cone {<0,0,0>, 1, <0,2,0>, 0 
 pigment {color Red}
 finish {Dull}}

#declare TurnedBox = box {<-1,-1,-1>, <1,1,1>
 pigment {color Blue}
 finish {Dull}
 rotate 45*z
 translate <1,1.75,0>
}

#declare ChoppedCone = difference {
object {SimpleCone}
object {TurnedBox}
}

object {ChoppedCone}


which looks like this:

difference.jpg

Note that any new surfaces formed by the "chopping" object take its texture. Again, this is one of those apparent nuisances that's really useful a lot of the time. For example, you can make a telescope mirror by taking a cylinder and then using it in a difference with a sphere or paraboloid with a reflective finish.

The intersection statement

intersection takes two or more objects and throws away everything that's not common to the insides of both. That's a little hard to explain verbally, so let's do it with pictures, using the spheres that illustrated the merge:

#include "colors.inc"
#include "textures.inc"
#include "finish.inc"

camera {location <0,1,-4> look_at <0,0.4,0>}
light_source {<0,40,-20> color White}
plane {y, -4 pigment {color White} finish {Dull}}

#declare Pair1 = union {
 object {SimpleSphere1}
 object {SimpleSphere2 translate <0.35,0,0>}
 }

#declare Pair2 = intersection {
 object {SimpleSphere1}
 object {SimpleSphere2 translate <0.35,0,0>}
 }

object {Pair1 translate <-1,0,0>}
object {Pair2 translate <1,0,0>}


intersect.jpg

The parts of the spheres that overlap in the union constitute the intersection.

Another way to look at it: merge + intersection = union

A problem with CSG

When the surfaces of two objects coincide exactly, POV-Ray sometimes cannot distinguish between them at the interface. This leads to something called "coincident surface artifacts". Here's an example: a transparent, light pink cone sitting on a blue cube:

#include "colors.inc"
#include "finish.inc"

camera {location <5,10,-8> look_at <0,0,0> angle 35}
light_source {<10,50,-10> color White}
light_source {<20,-40,20> color White}

background {color White}

#declare Thing = union {
 
 box {<-1,-1,-1>, <1,1,1> pigment {color Blue}}
 cone {<0,1,0>, 1, <0,5,0>, 0
   texture {pigment {color rgbf <1,0.9,0.9,1>}
            finish {ambient 0 diffuse 0 reflection 0.2
                    specular 0.2 roughness 0.001 
                    refraction 1 ior 1.05}
           }
 }
}
object {Thing}


Rendered, this becomes:

coinsurf.jpg

You can see that there are ugly speckles at the places where the cone meets the box: since both objects have a surface in exactly the same place, POV-Ray sometimes can't tell them apart, and so some points get the texture of the cube, others that of the cone.

To solve this, simply make one of the objects slightly larger or smaller so that there's a little overlap. Here, make the box very slightly taller:

box {<-1,-1,-1>, <1,1.001,1> pigment {color Blue}}


leaving the rest of the file alone. Now, we get:

coinsurf2.jpg

The change of 0.001 units has no visible effect on the size of the box, but the ugly coincident surface artifacts are gone. In general, it's always a good idea to have objects overlap slightly.

Simple Programming: #if ... #end and #while ... #end

POV-Ray supports some limited programming functions. Of these, the most important are the #if and the #while statements.

Using #if

Use #if to create simple "branches" in your POV-Ray rendering. An #if statement must contain an #if statement and an #end, and optionally #else. Let's look at an example:

#include "colors.inc"
#include "textures.inc"
#include "finish.inc"

camera {location <0,0,-80> look_at <0,0,0> angle 30}
light_source {<0,0,-50> color White}

#declare Flag = -1
#if (Flag > 0)
box {<-10,-10,0>, <10,10,0.1> pigment {White_Marble}}
#else
box {<-10,-10,0>, <10,10,0.1> pigment {Jade}}
#end


Render this, and we get:

if.jpg

If the statement after the #if evaluates as "true", then POV-Ray processes whatever is between the #if statement and the #else statement, if one is present. If the statement evaluates as "false", POV-Ray processes the portion between the #else statement, if there is one, and #end. After either case, POV-Ray skips to the line after #end and proceeds from there.

In our example, the expression Flag > 0 is false, so POV-Ray processed whatever followed the #else; in this case, coloring the box with the "Jade" pigment. Had Flag been set to, say, 2, POV-Ray would have used the "White_Marble" pigment instead:

if2.jpg

Use #if whenever you can set up simple "control pathways" for your scene, depending on what values important variables have.

Using #while

Use #while to create simple loops. For example, let's take a look at our mini-Stonehenge scene file from a while back. There were a whole bunch of lines that looked like:

object {Block rotate y*45}


We can use a #while, along with a counter, to automate this process. Here's how:

#declare Block = 
     box {<0,0,0>, <0.5,2,1> 
     pigment {color Gray80}
     translate <5,0,0>}

#declare Count = 0
#declare Steps = 12
#declare Angle = 360/Steps

#while (Count < Steps)

object {Block rotate y*Count*Angle}
#declare Count = Count + 1

#end


What does this code do? First, it defines the gray standing block. Then, it defines three variables using #declare: Count, which is the loop counter; Steps, which is the number of loop increments, and Angle, the amount to rotate the standing stone during each step. The #while evaluates the statement following it and only executes the rest of the loop (between the #while and the #end) if the statement is true. In this case, the loop continues until Count is equal to or greater than 12. Once Count is that high, POV-Ray jumps immediately to whatever follows the #end statement.

What does the loop itself do? First, it creates a Block object, which represents the standing stone, and rotates it by an amount proportional to the counter variable. Thus, each time the loop executes, it adds another stone further around the circle. Note that I've chosen Angle so that at the end of the loop, the rotation will have gone through a complete circle.

Second, the loop increments Count. This has to be done explicitly with a #declare. It's easy to forget this and inadvertently set up an infinite loop.

Anyway, let's see what this scene looks like:

minihenge3.jpg

Let's make one small change to the code: change Steps to 16, and re-render:

minihenge4.jpg

Wow. All that changed was one silly little digit.

Use #while any time you anticipate doing anything repetitive, like moving copies of objects to regularly spaced positions.

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer