Showing posts with label #povray #python #pysanky. Show all posts
Showing posts with label #povray #python #pysanky. Show all posts

Sunday, January 9, 2011

Prisms and Trigonometric Shapes - POV-ray

I was looking for a way to put a four petal flower shape on my Python-POV-ray generated egg.  As it turns out, this shape is a common geometric one called Pascal's Rose.

For expediency rather than efficiency's sake, I opted to generate the shape using individual x, y points (actually x, z in POV-ray):


def getcoords(theta, factorr, factortheta, trigfunc):
    """
    Returns two tuple of x and y values for
    the equation in the form

    factorr * r * r = trigfunc(factortheta * theta)
    """
    r = math.sqrt((trigfunc(factortheta * theta)) ** 2 / factorr)
    x = r * math.cos(theta)
    y = r * math.sin(theta)
    if abs(x) < ZEROLIMIT:
        x = 0.0
    if abs(y) < ZEROLIMIT:
        y = 0.0
    print x, y, theta
    return x, y

def cycleequation(step, start, stop, trigfunc, factorr, factortheta):
    """
    Returns list of x, y coordinates that correspond
    to the equation of the form

    factorr * r * r = trigfunc(factortheta * theta)

    theta will be incremented by step.
    start is the first value of theta.
    stop is the last value of theta.
    """
    coords = []
    theta = start
    while theta < stop:
        coords.append(getcoords(theta, factorr, factortheta, trigfunc))
        theta += step
    coords.append(getcoords(stop, factorr, factortheta, trigfunc))
    return coords

math.cos creates Pascal's Rose for this equation.  I had to add a Prism object to the POV-ray recipe I am using.

class Prism(Item):
  def __init__(self, base, top, numpts, *opts, **kwargs):
    Item.__init__(self,"prism",(base, top, numpts),opts,**kwargs)

For me it was easiest to just translate the points of the prism into POV-ray SDL language prior to feeding them to Python:

def translatetopovray(pts):
    """
    Returns string in povray format of list
    of points pts.
    """
    stringpoints = []
    for pt in pts:
        stringpoints.append('<%.8f, %.8f>' % pt)
    return string.join(stringpoints, ',')

This allowed for generation of a prism:

This is just a rough representation of what I wanted.  The scene I was creating required a two colored flower, the outside as gold and the inside as orange.  Further, the outside edge needed to appear to be of constant width, as one would achieve by applying wax to an egg with the pysanky drawing tool, the kistka.

To get this effect I used a rough offset algorithm - get the centerpoint of the line segment on the outside edge of the "flower", offset a set distance along the normal to the outside line segment, then connect all the inner points.

def gettan(pt1, pt2):
    """
    Returns the tangent of the directional
    angle formed by line segment pt1-pt2.
    """
    return (pt2[1] - pt1[1])/(pt2[0] - pt1[0])

def getmidpoint(pt1, pt2):
    """
    Get the midpoint of the line segment
    pt1-pt2.
    """
    x = pt1[0] + (pt2[0] - pt1[0])/2.0
    y = pt1[1] + (pt2[1] - pt1[1])/2.0
    return x, y

def calcoffsetpoint(pt1, pt2, offset):
    """
    Get point offset distance offset
    from the midpoint of line segment
    pt1-pt2 to the left.
    """
    midpoint = getmidpoint(pt1, pt2)
    # get normal
    # quadrant I
    if pt2[0] >= pt1[0] and pt2[1] >= pt1[1]:
        theta = math.atan(gettan(pt1, pt2)) + math.pi/2.0
        x = midpoint[0] + math.cos(theta) * offset
        y = midpoint[1] + math.sin(theta) * offset
    # quadrant II
    elif pt2[0] <= pt1[0] and pt2[1] >= pt1[1]:
        theta = math.atan(gettan(pt1, pt2)) + math.pi/2.0
        x = midpoint[0] - math.cos(theta) * offset
        y = midpoint[1] - math.sin(theta) * offset
    # quadrant III
    elif pt2[0] <= pt1[0] and pt2[1] <= pt1[1]:
        theta = 3.0 * math.pi/2.0 + math.atan(gettan(pt1, pt2))
        x = midpoint[0] + math.cos(theta) * offset
        y = midpoint[1] + math.sin(theta) * offset
    # quadrant IV
    elif pt2[0] >= pt1[0] and pt2[1] <= pt1[1]:
        theta = 3.0 * math.pi/2.0 + math.atan(gettan(pt1, pt2))
        x = midpoint[0] - math.cos(theta) * offset
        y = midpoint[1] - math.sin(theta) * offset
    else:
        print pt1, pt2
        print 'error - theta undefined'
        theta = 0.0
    return x, y

def getmiddle(step, start, stop, trigfunc, factorr, factortheta, offset):
    """
    Return list of points for middle of 'petal'.
    """
    startend = (0.0, 0.0)
    pts = [startend]
    # need to offset by fixed distance along
    # normal to each line segment
    # need to get pts along original flower
    # but not near 0.0, 0.0 where they will
    # overlap
    outsidepts = cycleequation(step, start, stop,
        trigfunc, factorr, factortheta)
    counter = 0
    maxcounter = len(outsidepts)
    while (counter + 1) < (maxcounter - 1):
        pts.append(calcoffsetpoint(outsidepts[counter],
            outsidepts[counter + 1], offset))
        counter += 1
    pts.append(startend)
    return pts

My analytical geometry is a bit rough, but this works well enough for what I was trying to accomplish.

Through some intersecting and positioning, I was able to get the effect I wanted.  This egg design, according to the Sixty Score of Easter Eggs book by Elyjiw, is from the Odessa region of the Ukraine.


Thanks for having a look.



Sunday, December 26, 2010

POV-ray + Python - Scene Creation

Brief recap:  this recipe from the Python cookbook webpage is the basis for an api for POV-ray from Python.  I added an Object class to the api for more flexibility in manipulating pre-made POV-ray objects.

This final post in the POV-ray series deals with an attempt to create a scene.  I want to draw some pysanky eggs on a board with pegs in it for the eggs to stand on.

For the wood texture of the board I've used T_Wood10 from the POV-ray woods.inc library.  This requires the inclusion of the woods.inc library in the Python code:

file = pov.File('test5.pov', 'colors.inc', 'woods.inc')

test5.pov is my output file; colors.inc and woods.inc are my include files.

Because the api deals exclusively with text, the wood texture will have to be put in as a string literal complete with POV-ray's curly brackets:

testblock = pov.Box((-15, -6.50, -18), (15, -2.00, -3), 
    texture = '{T_Wood10}')

As for the scene itself:
This is good for illustrating the scene's design, but it's not very realistic.  The eggs look like they're wrapped in cellophane.  I tried introducing some focal blur:

cam = pov.Camera(location = (0, 5, -35), look_at = (0, 0, 2),
    focal_point = (0, 2, -10), aperture = 0.4, blur_samples = 20)

This looks more realistic, but blurry.  It would probably serve a remote corner of a scene better rather than the main object.

At this point it's really the nuances of POV-ray scene creation that will make a difference.  The purpose of this post and the last three was to show that you can do some fun things with POV-ray from Python.

Lastly, and just for fun, some wooden eggs (textures textures.inc/Cork, woods.inc T_Wood10 and T_Wood14):

Sunday, December 5, 2010

POV-ray

I messed around with POV-ray a bit about five years ago and recently tried to resurrect some of that code.

There's a recipe for a POV-ray - Python API by Simon Burton out on ActiveState that I wanted to try.

Here is the shape I was trying to re-create with the Python API:
Literally, an Easter egg, a bit involved, but not overly complex.  The egg shape is borrowed from Friedrich Lohmüller's POV-ray site.

There is a simple example in the API which I've slightly modified to make a partially lit sphere:

# renamed recipe as pypov
import pypov as pov

file = pov.File('test2.pov', 'colors.inc')
cam = pov.Camera(location = (0, 1, -5), look_at = (0, -0.5, 2))
sphere = pov.Sphere((0, 0, 0), 1.5, pov.Texture(pov.Pigment(color = 'Blue')))
light = pov.LightSource((2, 4, -3), color = 'White')
file.write(cam, sphere, light)

This, after its output is run through POV-ray, yields this:
It won't win any animation awards, but it's pretty nonetheless.

The code for Lohmüller's egg shape looks like this:


# renamed recipe as pypov
import pypov as pov

file = pov.File('test3.pov', 'colors.inc')
cam = pov.Camera(location = (0, 1, -5), look_at = (0, -0.5, 2))
sphereupper = pov.Sphere((0, 0, 0), 1.0, pov.Texture(pov.Pigment(color = 'Blue')), scale = (1, 1.55, 1))
slabupper = pov.Box((-1, -1.55, -1), (1, 0, 1))
diffupper = pov.Difference(sphereupper, slabupper)
spherelower = pov.Sphere((0, 0, 0), 1.0, pov.Texture(pov.Pigment(color = 'Blue')), scale = (1, 1.15, 1))
slablower = pov.Box((-1, 0, -1), (1, 1.15, 1))
difflower = pov.Difference(spherelower, slablower)
union = pov.Union(difflower, diffupper, translate = (0, 0.55, 0), scale = 1.0)
light = pov.LightSource((2, 4, -3), color = 'White')
light2 = pov.LightSource((-2, -4, -3), color = 'White')
file.write(cam, union, light, light2)





and the output looks like this:


That is about as far as I got with the Python API.  The problems I was having were related to trying to shoehorn my POV-ray code into the API.  I added an Object class for the purpose of assigning attributes to predefined shapes.  The problem there is that you can't use the same keyword more than once (translate, then  rotate, then translate again).



Going forward I plan to work with simpler shapes (merging two parts instead of 50 or so).  Also, I'll need to leverage what the API offers against working within its limitations.  It will not be a one to one code translation between POV-ray and Python.