'''
delaunay and voronoi | for IAM dm2_w17 @ _diag
simplified version | for IAM dm1_s18 @ _diag
'''

#___________________________________________________________________________#
import RhinoPython.Host as _host
import Rhino, math, random, time
import scriptcontext
import rhinoscriptsyntax as rs
#___________________________________________________________________________#


def esc(wo=""):
    if _host.EscapePressed(0):
        raise Exception(wo+'***ESCape*Key*Pressed***')

global timeX
timeX = time.time()

def getTime(verbose=0):
    global timeX
    tim = float(time.time())
    tim -=  float(timeX)
    if verbose: print str(round(tim,2))+"secs"
    return round(tim,1)
    
def setTime():
    global timeX
    timeX = time.time()


def circumcircleOfTriangle(p0, p1, p2, verbose = 0):
    '''
    arguments:
        p0, p1, p2 .. 3 coords
        verbose 0/1 generate circle
    returns:
        list [center, radius]
    '''
    lineA = symmetryLine(p0, p1)
    lineB = symmetryLine(p1, p2)
    center= rs.LineLineIntersection(lineA, lineB)[0]
    if verbose:
        rs.AddCircle(center, rs.Distance(center,p0) )
    return [center, rs.Distance(center,p0)]
    
def centerOfLine(p0, p1):
    return rs.VectorAdd(p0, rs.VectorScale(rs.VectorSubtract(p1,p0), .5))
    
    
def symmetryLine(p0, p1):
    return [centerOfLine(p0, p1), rs.VectorAdd(centerOfLine(p0, p1), rs.VectorRotate(rs.VectorSubtract(p0,p1), 90.0, [0,0,1])) ]
#rs.AddCurve( symmetryLine(p0, p1), 1)

def pntCentroid( coords ) :
    x_sum = 0
    y_sum = 0
    z_sum = 0
    for coord in coords :
        x_sum += coord[ 0 ]
        y_sum += coord[ 1 ]
        z_sum += coord[ 2 ]
    num = len( coords )
    x = x_sum / num
    y = y_sum / num
    z = z_sum / num
    coord = [ x, y, z ]
    return coord

##########
# ConvHull
# worx @ xy_plane only
def pntConvHull(coordList, verbose=0):
    avg=pntCentroid(coordList)
    start = a = min(coordList)
    coords = [start]
    for i in range(1,10001):
        esc()
        o = a
        a = avg
        for b in coordList:
            if (a[0]-o[0])*(b[1]-o[1])-(a[1]-o[1])*(b[0]-o[0]) < 0:
                a = b
        coords.append(a)
        if a == start:
            break
    if a == start:# or i == 1000:
        if verbose: print "*** convHull: i=",i,": len convCoords=", len(coords), "(from "+str(len(coordList))+"coords)"

    return coords


def randPoints(anzahl=10, bereich=1.0, nonPlanar = 0 ):
    coords = []
    for i in range(anzahl):
        cor = [random.uniform(0,bereich),random.uniform(0,bereich),random.uniform(0,bereich)*nonPlanar]
        coords.append(cor)
    rs.AddPoints( coords )
    print "### "+str(anzahl)+" randPoints done generated"
    return coords

### find first shortest line
def startDelaunay(ptList):
    rs.AddLayer("Default")
    rs.AddLayer("VORO::_delaunay", [0,220,20])
    rs.AddLayer("VORO::_voronoi", [0,220,220])
    rs.AddLayer("VORO::_delaunay::triangles", [0,0,200])
    rs.AddLayer("VORO::_delaunay::lines", [110,110,110])
    rs.AddLayer("VORO::_voronoi::crvs", [0,200,200])
    rs.AddLayer("VORO::_voronoi::srfs", [220,200,100])
    rs.AddLayer("VORO::_voronoi::pnts", [200,0,200])
    rs.AddLayer("VORO::_delaunay::pnts", [210,110,110])

    rs.DeleteObjects(rs.ObjectsByName("*line-dpt*"))
    rs.DeleteObjects(rs.ObjectsByName("*delaunayPoints_convHull*"))
    allCoords = []
    for i, pt in enumerate(ptList):
        rs.ObjectName(pt,"dpt_"+str(i))
        rs.ObjectLayer(pt, "VORO::_delaunay::pnts") 
        allCoords.append( rs.PointCoordinates(pt) )
    distList = []
    anz = len(ptList)
    convHull = rs.AddCurve( pntConvHull(allCoords, 0), 1) # for check if voro_points are inside in makeVoronoi
    rs.ObjectName(convHull, "delaunayPoints_convHull")
    rs.ObjectLayer( convHull, "VORO::_delaunay")
    for i in range(anz):
        for j in range(i+1, anz):
            distList.append( [rs.Distance(allCoords[i],allCoords[j]), ptList[i], ptList[j], allCoords[i], allCoords[j]] )
    distList = sorted(distList)
    p0=distList[0][3] # coord
    p1=distList[0][4]
    pt0 = rs.ObjectName(distList[0][1]) # name
    pt1 = rs.ObjectName(distList[0][2])
    rs.ObjectName( rs.AddLine(  p0, p1 ), "line-"+pt0+"-"+pt1)

def findDelaunayTriangle(pt0, pt1, ptList, makeDelaunaySrfs=1, makeVoronoiPts=1):
    new = 0
    cor0 = rs.PointCoordinates(pt0)
    cor1 = rs.PointCoordinates(pt1)
    tmpList = ptList[:] # copy !
    tmpList.remove(pt0)
    tmpList.remove(pt1)
    cnt=0
    #print len(tmpList)
    for p,ptX in enumerate(tmpList):
            corX = rs.PointCoordinates(ptX)
            circum = circumcircleOfTriangle(cor0, cor1, corX, 0)
            #rs.AddCurve( [cor0, cor1, corX], 1)
            #rs.ObjectLayer(rs.AllObjects()[0:2], "Default")
            #rs.UnselectAllObjects()
            #rs.SelectObjects(rs.AllObjects()[0:2])
            cen = circum[0]
            rad = circum[1]
            bingo = 1
            cnt+=1
            for pt in ptList:
                dist = rs.Distance(cen, rs.PointCoordinates(pt))
                if dist < rad-0.00000000001:
                    pass
                    bingo = 0
                    break
            if bingo:
                nam0 = "line-"+rs.ObjectName(ptX)+"-"+rs.ObjectName(pt0)
                nam1 = "line-"+rs.ObjectName(ptX)+"-"+rs.ObjectName(pt1)
                #rs.SelectObject(ptX)
                if len(rs.ObjectsByName("*line-"+rs.ObjectName(pt0)+"-"+rs.ObjectName(ptX))) + len(rs.ObjectsByName("*line-"+rs.ObjectName(ptX)+"-"+rs.ObjectName(pt0))) == 0:
                    rs.ObjectName(rs.AddLine(cor0,corX), nam0 )
                    rs.ObjectLayer( rs.AllObjects()[0], "VORO::_delaunay::lines")
                    #rs.AddCircle(cen, rad)
                    new+=1
                if len(rs.ObjectsByName("*line-"+rs.ObjectName(pt1)+"-"+rs.ObjectName(ptX))) + len(rs.ObjectsByName("*line-"+rs.ObjectName(ptX)+"-"+rs.ObjectName(pt1))) == 0:
                    rs.ObjectName(rs.AddLine(cor1,corX), nam1 )
                    rs.ObjectLayer( rs.AllObjects()[0], "VORO::_delaunay::lines")
                    #rs.AddCircle(cen, rad)
                    new+=1
                p0= int(str.split(rs.ObjectName(pt0), "_")[1])
                p1= int(str.split(rs.ObjectName(pt1), "_")[1])
                pX= int(str.split(rs.ObjectName(ptX), "_")[1])
                sortX = sorted([p0,p1,pX])
                nam = "delaunay_3angle-dpt_"+str(sortX[0])+"-dpt_"+str(sortX[1])+"-dpt_"+str(sortX[2])
                if 1:
                    tmp = rs.ObjectsByName("delaunay_3angle-dpt_*")
                    _3angles = []
                    for item in tmp:
                        _3angles.append(rs.ObjectName(item))
                    if _3angles.count(nam) == 0:
                        srf = rs.AddCurve( [cor0, cor1, corX, cor0], 1 )
                        rs.ObjectName(srf, nam)
                        if makeDelaunaySrfs: # delaunay triangles
                            srf = rs.AddSrfPt( [cor0, cor1, corX] )
                            rs.ObjectName(srf, nam)
                        rs.ObjectLayer( srf, "VORO::_delaunay::triangles")
                if makeVoronoiPts: # voronoi pts
                    nam = "voronoi_pt-dpt_"+str(sortX[0])+"-dpt_"+str(sortX[1])+"-dpt_"+str(sortX[2])+"-"
                    tmp = rs.ObjectsByName("voronoi_pt-dpt_*")
                    _voroPts = []
                    for item in tmp: _voroPts.append(rs.ObjectName(item))
                    if _voroPts.count(nam) == 0:
                        poi = rs.AddPoint(cen)
                        rs.ObjectName(poi, nam)
                        rs.ObjectColor(poi, [200,0,0])
                        rs.ObjectLayer(poi, "VORO::_voronoi::pnts" )


def makeDelaunay( ptList, stopAt=1001, makeDelaunaySrfs=1, makeVoronoiPts=1 ):
    startDelaunay( ptList )
    cnt=0
    while rs.ObjectsByName("line-dpt*") and cnt < stopAt:
        esc()
        cnt+=1
        for line in rs.ObjectsByName("line-dpt*"): # 
            points = str.split(rs.ObjectName(line), "-")
            pt0 = rs.ObjectsByName(points[1])[0]
            pt1 = rs.ObjectsByName(points[2])[0]
            new = findDelaunayTriangle(pt0, pt1, ptList, makeDelaunaySrfs=makeDelaunaySrfs,makeVoronoiPts=makeVoronoiPts )
            rs.ObjectName(line, "delaunay_"+rs.ObjectName(line))
            if cnt%8==0: rs.Redraw()
    print "### delaunay done in %ss" % round(getTime(), 2)

def makeVoronoi( makeSrfs=1 ):
    voroPtNames = []
    for voroPt in rs.ObjectsByName("voronoi_pt*"):
        voroPtNames.append(rs.ObjectName(voroPt))
    for voroPt in voroPtNames:
        p0= str.split(voroPt, "-")[1]
        p1= str.split(voroPt, "-")[2]
        p2= str.split(voroPt, "-")[3]
        #print p0, p1, p2,
        ############################
        cnt=0
        if 0: # voronoi pass testing
            for nam in voroPtNames:
                cnt= nam.count("-"+p0+"-") + nam.count("-"+p1+"-") + nam.count("-"+p2+"-")
                cnt= nam.count(p0+"-") + nam.count(p1+"-") + nam.count(p2)
                if cnt==2:
                    #print nam.count(p0),nam.count(p1),nam.count(p2), "..", cnt,
                    #rs.SelectObject(rs.ObjectsByName(nam))
                    voroLine = rs.AddLine( rs.PointCoordinates(rs.ObjectsByName(voroPt)[0]), rs.PointCoordinates(rs.ObjectsByName(nam)[0]))
                    rs.ObjectColor(voroLine, [200,0,0])
                    #print voroPt,"---",nam
                    break
        ############################
    #ptX = "dpt_2"
    delaunayPts = rs.ObjectsByName("dpt_*")
    for ptX in delaunayPts:
        #print rs.ObjectName(ptX),
        ptX = rs.ObjectName(ptX)
        coords = []
        isInside = 1
        for voro in voroPtNames:
            #print voro, "\t", voro.count(ptX)
            if voro.count("-"+ptX+"-"):
                #rs.SelectObject( rs.ObjectsByName(voro)[0] )
                #print rs.PointCoordinates( rs.ObjectsByName(voro)[0] )
                coord = rs.PointCoordinates( rs.ObjectsByName(voro)[0] )
                coords.append(coord )
                isInside *= rs.PointInPlanarClosedCurve(rs.ObjectsByName(voro)[0], rs.ObjectsByName("delaunayPoints_convHull")[0], plane=None, tolerance=None)
                #print isInside,
        #print coords
        if len(coords) > 2:# and isInside:
            crv1 = rs.AddCurve( pntConvHull(coords, 0), 1 )
            rs.ObjectName( crv1, "voroCrv_"+ptX)
            rs.ObjectLayer(crv1, "VORO::_voronoi::crvs")
            if isInside:
                rs.ObjectColor(crv1, [100,20,61])
                rs.MoveObjects([crv1],  [0,0,0.0002] )
                rs.ObjectLayer( [crv1], "VORO::_voronoi::crvs" )
            if makeSrfs:
                crv2 = rs.AddCurve( pntConvHull(coords, 0), 2 )
                rs.ObjectLayer( crv2, "VORO::_voronoi::srfs" )
            if makeSrfs == 1:
                srf  = rs.AddPlanarSrf( crv2 )
                rs.MoveObjects([crv2, srf],  [0,0,0.0001] )
                rs.ObjectColor(srf, [200,200,205])
                rs.ObjectLayer( srf, "VORO::_voronoi::srfs" )
                if isInside:
                    rs.ObjectColor(srf, [100,160,160])
                    rs.ObjectLayer( srf, "VORO::_voronoi::srfs" )
            if makeSrfs == 2:
                srf  = rs.AddLoftSrf( [crv1, crv2] )
                rs.ObjectColor(srf, [200,220,60])

    if not voroPtNames:
        print "!!! no voroPoints finded: must activate \'makeVoronoiPts\' at \'makeDelaunay\' !!!"
    else: print "### voronoi  done in %ss" % round(getTime(), 2)
            



print "'''''''''''''''''''''''''''''''''''"
print "''' delaunay_lib 4 I OI III DM1/DM2"
print "''' lib ver simple | 20221207 _diag"
print "'''''''''''''''''''''''''''''''''''"