### https://www.student.tugraz.at/florian.bereiter/dm2/hue_03/hu_03_WIP.py
### grabbed:  2023-10-31 10:17:40
#################################


##############################
###  DM2_w23  hu_03_setUp  ###
###  _diag  /  2023 10 20  ###
##############################
import rhinoscriptsyntax as rs
import random, time, sys   ###                                              
sys.path.append("P:/")     ### add path .. setUp_hu_02.py needed !!!                                              
sys.path.append("P:/WWW/flobber27/dm2")     # path for library !!
import DM_lib as dm        ### reload(dm)    
##############################   

rs.UnitSystem(3)                       
rs.ShowGrid(view=None, show=0)
rs.ShowGridAxes(view=None, show=0)
rs.ViewDisplayMode(view=None, mode="wireframe")
rs.EnableRedraw(0)
dm.PointRadius(displayModeX=0, rad=3, styl=3)
rs.DeleteObjects(rs.AllObjects())
dm.printDisplay(1)

######################################           
anz = random.choice( range(2**4, 2**8, 4) )
coordsCub = dm.setUp_hu_02( anz )[1]  ### calling def from DM_lib to get *new* set of coords
######################################

randomVec = [random.uniform(-30,30) for i in range(3)]
coordsCub = [rs.VectorRotate(cor, randomVec[0], randomVec) for cor in coordsCub]

siz = rs.Distance( coordsCub[0],  coordsCub[1] )
vecX = rs.VectorSubtract( coordsCub[1],  coordsCub[0] )
vecY = rs.VectorSubtract( coordsCub[-1],  coordsCub[0] )
vecZ = dm.normVec3pnts(coordsCub[0], coordsCub[1], coordsCub[-1]) ### unitVector / length = 1.0
vecZ = rs.VectorScale( vecZ, siz )

ptA = coordsCub[0]
ptB = rs.VectorAdd( ptA, rs.VectorScale( vecX, anz/4) )
ptC = rs.VectorAdd( ptB, rs.VectorScale( vecY, anz/4) )
ptD = rs.VectorAdd( ptC, rs.VectorScale( vecX, -anz/4) )

ptE = rs.VectorAdd( ptA, rs.VectorScale( vecZ, anz/4) )
ptF = rs.VectorAdd( ptB, rs.VectorScale( vecZ, anz/4) )
ptG = rs.VectorAdd( ptC, rs.VectorScale( vecZ, anz/4) )
ptH = rs.VectorAdd( ptD, rs.VectorScale( vecZ, anz/4) )

###

if 0:   # shows corner points of cube and  x y z axes of cube
    baseCrv = rs.AddCurve( [ptA, ptB, ptC, ptD, ptA], 1 ) 
    topCrv  = rs.AddCurve( [ptE, ptF, ptG, ptH, ptE], 1 ) 
    for i,car in enumerate(["A","B","C","D","E","F","G","H"]):
        rs.AddTextDot( car, [ptA, ptB, ptC, ptD,ptE, ptF, ptG, ptH][i] )

    rs.ZoomExtents()
    dirX = rs.CurveArrows (rs.AddLine(ptA, ptB), 2)
    dirY = rs.CurveArrows (rs.AddLine(ptA, ptD), 2)
    dirZ = rs.CurveArrows (rs.AddLine(ptA, ptE), 2)
    rs.ObjectColor(rs.AllObjects()[2], [200,20,20] )
    rs.ObjectColor(rs.AllObjects()[1], [20,200,20] )
    rs.ObjectColor(rs.AllObjects()[0], [20,20,200] )
    
    coords = rs.DivideCurve(baseCrv, anz, 0)
    coords += rs.DivideCurve(topCrv, anz, 0)
    rs.DeleteObjects( [baseCrv, topCrv] )

### here we go:

### initial idea

if 0:   # left in for memory/ starting idea - creates circle parallel to cubeplane that is shifted randomly in that plane, creates sphere in center of cube and satellite spheres around that - makes interesting scenes
    cubelength = rs.Distance(ptA, ptB)
    circleshift_0 = 0.2
    circleshift_1 = 0.8
    
    cen = dm.pntInbetween(ptA, ptG)
    
    midAE = dm.pntInbetween(ptA, ptE)
    midBF = dm.pntInbetween(ptB, ptF)
    
    #rs.AddPoint(cen)
    
    vec0 = rs.VectorSubtract(midAE, cen)
    vec0 = rs.VectorUnitize(vec0)
    vec1 = vec0
    vec0 = rs.VectorScale(vec0, cubelength*random.uniform(circleshift_0,circleshift_1))
    
    vec1= rs.VectorScale(vec1, cubelength*random.uniform(circleshift_0,circleshift_1))
    
    vec2 = rs.VectorSubtract(midBF, cen)
    vec2 = rs.VectorUnitize(vec2)
    vec2 = rs.VectorScale(vec2, cubelength*random.uniform(circleshift_0,circleshift_1))
    
    pt0 = rs.VectorAdd(cen, vec0)
    #rs.AddPoint(pt0)
    
    pt1 = rs.VectorSubtract(cen, vec1)
    #rs.AddPoint(pt1)
    
    pt2 = rs.VectorAdd(cen, vec2)
    #rs.AddPoint(pt2)
    midcirc = rs.AddCircle3Pt(pt0, pt1, pt2)
    
    circdots = rs.DivideCurve(midcirc, 50, 0)
    #dm.textDots(circdots)
    
    dist_list = []
    for cor in circdots:
        dist = rs.Distance(cen, cor)
        dist_list.append([dist, cor])
        
    dist_list.sort()
    
    min_dist = dist_list[0][1]
    
    vec_md = rs.VectorSubtract(min_dist, cen)
    vec_midsphere = rs.VectorScale(vec_md, 0.7)
    coords = []
    for i in range(1001):
        scalVec = rs.VectorRotate( vec_midsphere, random.uniform(-180,180), [random.uniform(-1,1) for i in range(3)])
        coords.append( rs.VectorAdd( cen, scalVec ) )
    rs.AddPoints( coords )
    vec_ran = rs.VectorScale(vec_md, random.uniform(0.1, 0.25))
    ran_sat = random.choice(circdots)
    coords = []
    for k in range(5):
        ran_sat = random.choice(circdots)
        vec_ran = rs.VectorScale(vec_md, random.uniform(0.1, 0.3))
        sat = []
        for i in range(101):
            scalVec = rs.VectorRotate( vec_ran, random.uniform(-180,180), [random.uniform(-1,1) for i in range(3)])
            sat.append( rs.VectorAdd( ran_sat, scalVec ) )
        coords.append(sat)
    for j in range(len(coords)):
        rs.AddPoints( coords[j] )
    rs.AddCurve([cen, min_dist])

### touching spheres

anz = 16        # density of cubeGrid, number of points on 1 edge

abstand = rs.Distance(ptA, ptB)/(anz-1)
vec_x = rs.VectorSubtract(ptB, ptA)
vec_x = rs.VectorUnitize(vec_x)
vec_y = rs.VectorUnitize(rs.VectorSubtract(ptD, ptA))
vec_z = rs.VectorUnitize(rs.VectorSubtract(ptE, ptA))

pt_x = rs.VectorAdd(ptA, vec_x)

cubeGrid = []
for x in range(anz):
    vec = rs.VectorScale(vec_x, abstand*x)
    pt_x = rs.VectorAdd(ptA, vec)
    #rs.AddPoint(pt_x)
    for y in range(anz):
        pt_y = rs.VectorAdd(pt_x, rs.VectorScale(vec_y, abstand*y))
        #rs.AddPoint(pt_y)
        for z in range(anz):
            pt_z = rs.VectorAdd(pt_y, rs.VectorScale(vec_z, abstand*z))
            #rs.AddPoint(pt_z)
            cubeGrid.append(pt_z)

### loop ###

# starters #

cen_rad_list = []
sphere_list = []
new_Grid = cubeGrid[:]
inside = []
dist_list = []


num_of_spheres =    64         # sets number of spheres to create, if within boundaries ( either only a tenth of initial cubeGrid left - or - creates more than 25.000 points on spheresurfaces )
sphere_density =    1024       # sets number of points on large spheresurfaces, smaller ones are half of that, even smaller ones a quarter of initial number
rad =               1          # sets radius of initial sphere
allGrid = len(new_Grid)        # used to break loop if Grid gets reduced too much
max_sphere_points = 25000      # sets number of sphere_points when loop no longer adds another sphere


if 0:
    for i in range(num_of_spheres):
        #print i,"len(new_Grid)", len(new_Grid)
        if len(new_Grid) < allGrid*0.1:                 # breaks loop if less than a tenth of initial Grid is left
            print "________GRID__________brrrrrr"
            print len(new_Grid), ", thats too few"
            break
        if len(sphere_list) > max_sphere_points:        # breaks loop if too many points would be created
            print "______SPHERELIST______brrrrrr"
            print len(sphere_list), ", thats too many"
            break
        cen = random.choice(new_Grid)                   # chooses a random point in Grid to make center of new sphere
        #print len(cen_rad_list), rad
        
        if i > 0:                                       # if it is the second sphere or following, then this calculates the radius of the respective sphere
            dist_list=[]
            for j in range( len(cen_rad_list) ):                # checks list of all existing sphere centerpoints and sphere radii
                dist = rs.Distance(cen_rad_list[j][0], cen)     # calculates distance of new center to existing center in loop
                dist = dist - cen_rad_list[j][1]                # subtracts radius of existing sphere in loop to get distance from new center to surface of existing sphere
                dist_list.append(dist)                          # adds that distance to list
            dist_list.sort()                                    # sorts list to get shortest distance in place 0
            rad = dist_list[0]                                  # sets shortest distance as radius for new sphere - so sphere touches nearest existing sphere
            
        cen_rad_list.append([cen, rad])                         # adds new center and new radius to list, so next sphere can check for shortest distance again
        #print "rad", i, rad
    #    if len(cen_rad_list)>1:                                            # can be used to draw line between all spherecenters, currently unused
    #        lin = rs.AddLine( cen_rad_list[-1][0], cen_rad_list[-2][0] )
    #        rs.ObjectPrintWidth( lin, 1.0)
        vec = rs.VectorScale(rs.VectorUnitize([1,0,0]), rad)                # creates vector with length of radius, to be used when creating points on spheresurface
        sphere_density_loop = sphere_density                                # sets density of points on spheresurfaces for loop
        if rad < 1:                                                         # if radius of sphere less than 1, only half density, if less than .5 only quarter density - to reduce amount of points
            sphere_density_loop = int(sphere_density*0.5)  
        if rad < 0.5:
            sphere_density_loop = int(sphere_density*0.25)
        for j in range(sphere_density_loop):                                                                                # loop creating coordinates on spheresurface where points can later be added
            spherevec = rs.VectorRotate( vec, random.uniform(-180,180), [random.uniform(-1,1) for i in range(3)])
            sphere_list.append(rs.VectorAdd(cen, spherevec))
        
        
#        for cor in new_Grid:                        # loop checking coordinates on Grid, if within the new sphere, add coord to inside.list
#            dist = rs.Distance(cen, cor)            # not used, different way to write further down
#            if dist < rad:
#                inside.append(cor)
        #print len(inside)
        #inside = [cor for cor in new_Grid if rs.Distance(cen, cor) < rad]
        #new_Grid = list(set(new_Grid) - set(inside))
    
        #print "len new_Grid", len(new_Grid),
    
    #    for cor in inside:
    #        if cor in new_Grid:
    #            new_Grid.remove(cor)
        new_Grid = list(set(new_Grid) - set([cor for cor in new_Grid if rs.Distance(cen, cor) < rad]))      # checks distance to new center, if within radius removes coordinate from Grid
        #new_Grid = sorted(new_Grid, key=lambda cor: [cor[0],cor[1],cor[2]])                                
        #print "len new_Grid", len(new_Grid)
    
    rs.AddPoints(sphere_list)              # creates points of sphere surfaces
    #rs.AddPoints(new_Grid)                 # creates points of Grid after coords within spheres removed
    #rs.AddCurve(sphere_list, 2)            # creates curve along points on sphere surfaces
    #rs.AddCurve(sphere_list, 1)

################
rs.ZoomExtents()