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.



No comments:

Post a Comment