POV-ray Dice
2022 February 22

I saw a captcha with dice thread on hacker news. The general idea of the captcha was to find the picture of dice with the right sum on it. I was wondering how hard it would be to generate several thousand of these dice images, and how long it would take using POV-ray. POV-ray is the Persistence of Vision Ray Tracer. It takes a text based description of the scene built using various primitives like spheres and boxes to produce a ray traced image. The POV-ray language is Turing complete and allows some really advanced programming features, such as macros, loops and conditionals, which I take full advantage of for this scene. The dice are even transparent.

The dice

Benchmark and Estimation of Effort to Generate Dices for Captchas.

It took me roughly 6 hours with some exposure to POV-ray in the past to make the above scene. It takes roughly 10 seconds wall time to render on my 4 threaded laptop at 1600x900. Generating it at 400x400 takes approximately 2.5 seconds.

A captcha service should have at least a 100,000 variations on images to prevent abuse. So generation of the initial set of images would take roughly 69 and a half hours on my laptop assuming they were run as a batch process. Also using POV-ray would allow generation of new problems on the fly. This does not include writing a wrapper to modify the existing script to change the numbers on the fly.

Code

The following code was used to generate the above image. It uses a lot of macros and for-loops to improve understandability and brevity. Macros allow function like creation of objects and enables hiding of what transformations are required to put things where they need to be.

The dice body is made up of a series of intersections with cylinders on a cube, so that the cube has some nice looking rounded edges. The dots are spheres on each face and produced using a difference. Using these primitive shapes enable exact calculation of geometry, unlike using a mesh which is only an approximation. Building things like this is called Constructive Solid Geometry. It’s super useful for when things have to be precise.

To recreate the above image, run povray Width=1600 Height=900 dice.pov after copying the code into a file called dice.pov.


#version 3.6;
#include "colors.inc"
#include "textures.inc"

// some variables about lighting and things
#declare light_y = 40;
#declare light_x = 30;


global_settings { assumed_gamma 1.0 }

//background { color rgb <0.25, 0.25, 0.25> }

camera {
    location <10, 20, 20.0>
    direction 1.5*z
    right x*image_width/image_height
    look_at <0,0,0>
}

sky_sphere {
    pigment {
        gradient y
        color_map {
            [0 color Black]
            [1 color Blue]
        }
        scale 2
        translate -1
    }
    pigment {
        bozo
        turbulence 0.65
        octaves 6
        omega 0.7
        lambda 2
        color_map {
          [0.0 0.1 color rgb <0.85, 0.85, 0.85>
                   color rgb <0.75, 0.75, 0.75>]
          [0.1 0.5 color rgb <0.75, 0.75, 0.75>
                   color rgbt <1, 1, 1, 1>]
          [0.5 1.0 color rgbt <1, 1, 1, 1>
                   color rgbt <1, 1, 1, 1>]
        }
        scale <0.2, 0.5, 0.2>
    }
    rotate -135*x
}


// The sun light
light_source {
    <100,100,100>
    color White
    parallel
    point_at <0,0,0>
}

light_source {
    <0, light_y, 0>
    color <0.5, 0.5, 1> 
    spotlight
    radius 20
    falloff 25
    tightness 0
    point_at <0, 0, 0>
}



#declare Dot_Texture = texture {
    pigment { color White }
};

#macro My_Dice_Object_Centered_Origin(
        Dice_dot_size,  // The size of the dice dot
        Dot_offset_size, // The offset of the dots
        Body_Color
    )
#local Dice_Body_Texture = texture {
    pigment { color Body_Color filter 0.66  }
    finish { phong 0.8 }
    // normal { agate 0.25 scale 1/2 }
};
difference {
    intersection {
        intersection {
            intersection {
                box {
                    <-1, -1, -1>,
                    <1, 1, 1>
                }
                cylinder {
                    <0, -10, 0>,
                    <0, 10, 0>,
                    1.35
                    open
                }
            }
            cylinder {
                <-10,0,0>,
                <10,0,0>
                1.35
                open
            }
        }
        cylinder {
            <0,0,10>,
            <0,0,-10>
            1.35
            open
        }

        // Dicebody definition stuff
        interior {
            ior 1.5
        }
        texture { 
            Dice_Body_Texture
        }
    }
    // The 1 face
    sphere {
       <0,1,0>, Dice_dot_size
       texture { Dot_Texture }    
    }
    // The 6 face
    #for (Where_x, -1, 1, 2)
        #for (Where_z, -1, 1, 1)
            sphere {
               <Where_x * Dot_offset_size,-1, Where_z * Dot_offset_size>, Dice_dot_size
               texture { Dot_Texture }    
            }
        #end
    #end

    // 2 face
    sphere {
       <Dot_offset_size,Dot_offset_size,-1>, Dice_dot_size
       texture { Dot_Texture }    
    }
    sphere {
       <-Dot_offset_size,-Dot_offset_size,-1>, Dice_dot_size
       texture { Dot_Texture }    
    }
    // END 2 FACE

    // The 5 face is on the opposite side of the 2 face
    sphere {
        <0,0,1>, Dice_dot_size
        texture { Dot_Texture }
    }
    #for (Where_x, -1, 2, 2)
        #for (Where_y, -1, 2, 2)
            sphere {
               <Where_x * Dot_offset_size, Where_y * Dot_offset_size,1>, Dice_dot_size
               texture { Dot_Texture } 
            }
        #end
    #end

    // END 5 Face

    // 4 Face 
    #for (Where_x, -1, 2, 2)
        #for (Where_y, -1, 2, 2)
            sphere {
               <1.0, Where_x * Dot_offset_size, Where_y * Dot_offset_size>, Dice_dot_size
               texture { Dot_Texture }    
            }
        #end
    #end
    // End 4 face

    // The 3 face is on the opposite side of 4
    sphere {
       <-1.0,-Dot_offset_size,-Dot_offset_size>, Dice_dot_size
       texture { Dot_Texture }    
    }
    sphere {
       <-1.0,Dot_offset_size,Dot_offset_size>, Dice_dot_size
       texture { Dot_Texture }    
    }
    sphere {
       <-1.0,0,0>, Dice_dot_size
       texture { Dot_Texture }    
    }
    // End 3 face
}
#end

#macro My_Dice_Object(NumUp, Rot)
    object {
        #switch (mod(NumUp,3))
            #case (0)
                #local Body_Color = Red;
            #break
            #case (1)
                #local Body_Color = Green;
            #break
            #case (2)
                #local Body_Color = Blue;
            #break
        #end
        My_Dice_Object_Centered_Origin(0.2, 0.6, Body_Color)
        #switch(NumUp)
            #case (1)
            // no rotation needed
            #break
            #case (2)
            rotate <90,0,0>
            #break
            #case (3)
            rotate <0,0,-90>
            #break
            #case (4)
            rotate <0,0,90>
            #break
            #case (5)
            rotate <-90,0,0>
            #break
            #case (6)
            rotate <180,0,0>
            #break
        #else
            #warning "NumUp is out of expected range"
        #end
        rotate <0,Rot*35+9,0>
        translate <0, 1, 0>
    }
#end 

#declare Frame_Interior_Measure = 9;

// The Frame
box {
    <-10,2,-10>
    <10,-1,-Frame_Interior_Measure>
    texture { DMFWood6 
        rotate <0,90,0>
    }
}

box {
    <-10,2,-10>
    <10,-1,-Frame_Interior_Measure>
    texture { DMFWood6 
        rotate <0,90,0>
    }
    rotate <0,90,0>
}

box {
    <-10,2,10>
    <10,-1,Frame_Interior_Measure>
    texture { DMFWood6 
        rotate <0,90,0>
    }
}

box {
    <-10,2,10>
    <10,-1,Frame_Interior_Measure>
    texture { DMFWood6 
        rotate <0,90,0>
    }
    rotate <0,90,0>
}

// Frame base
box {
    <-Frame_Interior_Measure,0,-Frame_Interior_Measure>
    <Frame_Interior_Measure,-1,Frame_Interior_Measure>
    texture {
        checker texture {DMFLightOak}, texture {DMFWood6 rotate <0,90,0>}
    }
}

#for (Row, -1, 1)
#for (Col, -1, 1)
    #local rx = Row + 1;
    #local ry = Col + 1;
    #local Index = rx*3 + ry;
    #local MyDNum = mod(Index, 6) + 1 ;
    object {
        My_Dice_Object(MyDNum, Index+1)
        translate <5*Row, 0, Col*4>
    }
#end
#end

/*
#for (DiceNum,-2,3,1)
object {
    My_Dice_Object(DiceNum+3)
    #if (DiceNum < 0)
        translate <DiceNum*2.25, 0, 3>
    #else
        translate <DiceNum*2.25,  0, -3>
    #end
}
#end
*/
*****
Written by Henry J Schmale on 2022 February 22
Hit Counter

Remember you can also subscribe using RSS at the top of the page!

Share this on → Twitter LinkedIn Reddit