#!/usr/bin/env python

# These two lines are only needed if you don't put the script directly into
# the installation directory
import sys
sys.path.append('/usr/share/inkscape/extensions')

import math

import inkex

from simplestyle import *

import random

class blobsEffect(inkex.Effect):
    """
Creates a random blob from a convex hull over n points.
The expected degree of the polygon is sqrt(n).  The corners
are blunted by the blunt parameter.  0 means sharp. 1 will
result in loopy splines.
    """
    def __init__(self):
        """
        Constructor.
        Defines the parms option of a script.
        """
        # Call the base class constructor.
        inkex.Effect.__init__(self)

        # Define string option "--what" with "-w" shortcut and default "World".

        self.OptionParser.add_option('-n', '--num', action = 'store',
          type = 'int', dest = 'num', default = 25,
          help = 'Number of random points to start with')

        self.OptionParser.add_option('-b', '--blunt', action = 'store',
          type = 'float', dest = 'blunt', default = 0.3,
          help = 'Bluntness of corners. Should be < 1')

        self.OptionParser.add_option('-c', '--cave', action = 'store',
          type = 'float', dest = 'cave', default = 0.0,
          help = 'Concavity. Less blobby and more splatty')

        self.OptionParser.add_option('-x', '--rx', action = 'store',
          type = 'int', dest = 'rx', default = 1000,
          help = 'Size of work area x')

        self.OptionParser.add_option('-y', '--ry', action = 'store',
          type = 'int', dest = 'ry', default = 1000,
          help = 'Size of work area y')

        self.OptionParser.add_option('-z', '--sz', action = 'store',
          type = 'float', dest = 'sz', default = 50.,
          help = 'Size of a blob')

        self.OptionParser.add_option('-g', '--nb', action = 'store',
          type = 'int', dest = 'nb', default = 10,
          help = 'Total number of blobs')

        self.OptionParser.add_option("", "--Nmain", action="store",
          type="string", dest="active_tab", default='top',
          help="Active tab.")



    def effect(self):
        """
        Effect behaviour.
        Overrides base class' method.
        """

        global cave

        blunt = float( self.options.blunt )
        cave = float( self.options.cave )
        sz = float( self.options.sz )
        nb = int( self.options.nb )
        rx = int( self.options.rx )
        ry = int( self.options.ry )
        num = int( self.options.num )

        # Get access to main SVG document element and get its dimensions.
        svg = self.document.getroot()

        # Create a new layer.
        layer = inkex.etree.SubElement(svg, 'g')
        layer.set(inkex.addNS('label', 'inkscape'), 'Blob Layer' )
        layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')

        style = {
            'fill'          : '#000000'
            }
        #        inkex.errormsg("black")

        ctrs = [(random.randrange(rx) , random.randrange(ry))
                 for i in range(nb) ]
        for ctr in ctrs :
            points = [(random.gauss(ctr[0], sz) , random.gauss(ctr[1], sz))
                  for i in range(num) ]

            px = hull(points)
            pts = [points[px[i]] for i in range(len(px))]
        

            # Create path element
            path = inkex.etree.Element(inkex.addNS('path','svg'))



            path.set('style', formatStyle(style))
        
            pathstring = 'M ' + str(pts[0][0]) + ' ' + str(pts[0][1]) + ' '

            for j in range(len(pts)):
                k = (j+1) % len(pts)
                kk = (j+2) % len(pts)
                if j==0 :
                    (lasth, h1) = sHandles(pts[-1], pts[0], pts[1], blunt)
                (h2, hnext) = sHandles(pts[j], pts[k], pts[kk], blunt)
                pathstring += "C %f %f %f %f %f %f " % (h1[0], h1[1],
                                                        h2[0], h2[1],
                                                        pts[k][0], pts[k][1])
                h1 = hnext

            pathstring += 'Z'
            path.set('d', pathstring )
            layer.append(path)

def sHandles(pre, pt, post, blunt):
    '''I'm proud of this cute little construction for the
    spline handles. No doubt someone has thought of it before
    but, if not, its name is ACHC Andrew's Cute Handle
    Construction.  Note: no trig function calls.'''
    try :
        slope = (post[1] - pt[1]) / (post[0] - pt[0])
    except ZeroDivisionError :
        slope = math.copysign(1E30 , post[1] - pt[1])
    lenpre  = distance(pre, pt)
    lenpost = distance(pt, post)
    lenr = lenpre**2 / lenpost
    locx = math.copysign( lenr / math.sqrt(1. + slope**2) , post[0] - pt[0])
    mark = ( pre[0] - locx , pre[1] - locx*slope )
    try :
        markslope = (pt[1] - mark[1]) / (pt[0] - mark[0])
    except ZeroDivisionError :
        markslope = math.copysign(1E30 , pt[1] - mark[1])
    prex = math.copysign( lenpre / math.sqrt(1. + markslope**2) ,
                          pt[0] - mark[0])
    hpre = (pt[0] - prex*blunt , pt[1] - prex*markslope*blunt)
    postx = prex*lenpost/lenpre
    hpost = (pt[0] + postx*blunt , pt[1] + postx*markslope*blunt)
    return (hpre, hpost)

"""Blunt=0.3 makes pleasingly round, mostly convex blobs. 0.4 makes them more
concave. 0.6 - 1.0  they're getting more and more pointy. 2.0 - 10. and they
grow appendages like hot-air balloons.  0.1 makes the corners pretty sharp.
0.0 and it's down to the convex hulls that are the basis of the blobs, that
is, polygons"""


def distance(a, b) :
    return math.sqrt( (a[0] - b[0])**2 + (a[1] - b[1])**2  )


def hull(arg):
    """Convex hull by Graham scan."""
    xarr, yarr = zip( * arg )
    ymin = min(yarr)
    ind = findall( yarr, lambda y: y == ymin )
    if len(ind) > 1 :
        xshort = [xarr[j] for j in ind] 
        xmin = min(xshort)
        j = ind[xshort.index(xmin)]
        ind = j
    else :
        ind = ind[0]
    all = range(len(xarr))
    del all[ind]
    all.sort(key=lambda i : (xarr[i] - xarr[ind]) /
             math.sqrt( (xarr[i] - xarr[ind])**2 + (yarr[i] - yarr[ind])**2 ),
             reverse=True)
    if len(all) < 3 :
        all.insert(0, ind)
        return all
    ans = [ind]
    for i in all :
        if len(ans) == 1 :
            ans.append(i)
        else :
            while rightTurn(ans[-2], ans[-1], i, arg) :
                ans.pop()
            ans.append(i)

    return ans

def rightTurn(j, k, l, arg) :
    '''Cross product: Ax*By - Ay*Bx = Cz  '''
    ax = (arg[k][0] - arg[j][0])
    by = (arg[l][1] - arg[k][1])
    ay = (arg[k][1] - arg[j][1])
    bx = (arg[l][0] - arg[k][0])
    p = ax*by - ay*bx
    dot = ax*bx + ay*by
    cos = dot / math.sqrt((ax**2 + ay**2) * (bx**2 + by**2))
    crt = 1 - cave*2
    
    if p <= 0  :
        return cos < crt #We forgive right turns based on /cave/
    else :
        return False




def findall(a, f):
    r = []
    for x, j in zip(a, range(len(a))) :
        if f(x) :
            r.append(j)
    return r


# Create effect instance and apply it.
effect = blobsEffect()
effect.affect()
sys.exit( 0 )

