Nebula

Lots and lots of copies of vector paths in NodeBox.

 

nebula1

nebula2

nebula4

 

NodeBox source code:

size(700,700)
 
def angle(x0, y0, x1, y1):
    from math import degrees, atan2
    a = degrees( atan2(y1-y0, x1-x0) )
    return a
 
def distance(x0, y0, x1, y1):
    from math import sqrt, pow
    return sqrt(pow(x1-x0, 2) + pow(y1-y0, 2))
 
def coordinates(x0, y0, distance, angle):
    from math import radians, sin, cos
    x1 = x0 + cos(radians(angle)) * distance
    y1 = y0 + sin(radians(angle)) * distance
    return x1, y1
 
def reflect(x0, y0, x1, y1, d=1.0, a=180):
    d *= distance(x0, y0, x1, y1)
    a += angle(x0, y0, x1, y1)
    x, y = coordinates(x0, y0, d, a)
    return x, y
 
def merge(points, x, y, vx, vy, reflected=False, d=0):
    """ Smooth curves from points to x, y.
    """
    beginpath(0,0)
    for pt in points:
        if not reflected:
            vx0, vy0 = pt.ctrl1.x, pt.ctrl1.y
        else:
            vx0, vy0 = reflect(pt.x, pt.y, pt.ctrl1.x, pt.ctrl1.y)
        moveto(pt.x, pt.y)
        curveto(vx0, vy0, vx, vy, x+random(-d,d), y+random(-d,d))
    return endpath(draw=False)
 
def points(n, vx, vy, h=(0.0,1.0), v=(0.0,1.0)):
    """ Random points in relative h,v box with vx, vy handles.
    """
    points = []
    for i in range(n):
        pt = PathElement()
        pt.x = WIDTH*h[0] + random(WIDTH*(h[1]-h[0]))
        pt.y = HEIGHT*v[0] + random(HEIGHT*(v[1]+v[0]))
        pt.ctrl1.x = pt.x+vx
        pt.ctrl1.y = pt.y+vy
        points.append(pt)
    return points
 
 
def nebula(clr, 
           bg=True, 
           n=100, 
           d=300, 
           angle=0.5, 
           iterations=100, 
           tonality=0.1,
           growth=[1.01, 1.01, 0.98]):
 
    def _wrap_around(n, base=1):
        if n < 0: return base-n
        if n > base: return n-base
        return n
        
    def _draw_transformed(path, dx=0, dy=0, angle=0, scaling=1.0):
        rotate(angle)
        drawpath(BezierPath(path.path))
        scale(scaling)
        translate(dx, dy)        
 
    colormode(HSB)
    if bg:
        background(
            clr.hue, 
            clr.saturation, 
            max(0.15, clr.brightness*0.15)
        )
    
    strokewidth(0.1)
    nofill()
    autoclosepath(False)
 
    # Create a number of points in a portion of the canvas.
    # The points all have the same handle vector.
    # Then, draw a line from each point to a focus point.
    h = (random(1-random(0.2)), random(0.2))
    v = (random(1-random(0.2)), random(0.2))
    pts = points(n, random(-d,d), random(-d,d), h, v)
    x, y = random(WIDTH), random(HEIGHT)
    vx, vy = x+random(-d/2,d*2), y+random(-d/4,d/4)
    path = merge(pts, x, y, vx, vy, d=random(d/2))
 
    # Draw rotating versions of this path
    # that increment in size and horizontal position.
    # Colors are dark with variations in hue.
    #transform(CORNER)
    direction = choice((-1,1))
    for i in range(iterations/2):
        h = _wrap_around((clr.hue + random(tonality)*direction))
        s = clr.saturation
        b = clr.brightness * (0.4 + random(0.6))
        stroke(h, s, b, random(0.25))
        _draw_transformed(path, 1.5, 0, angle, growth[0])
 
    for i in range(choice((2,3))):
        reset()
        
        # Draw lines from the foucs point to new points,
        # with their handles reflected from the focus' handles.
        h = (random(1-random(0.2)), random(0.2))
        v = (random(1-random(0.2)), random(0.2))
        pts = points(n, random(-d,d), random(-d,d), h, v)
        vx, vy = reflect(x, y, vx, vy)
        path = merge(pts, x, y, vx, vy, reflected=True, d=10)
        
        # Incremental variations of the path.
        # Colors are light and desaturated.
        for i in range(iterations/2):
            h = clr.hue
            s = clr.saturation + random(-0.6)
            b = random(0.2) + 0.8
            stroke(h, s, b, random(0.25))
            _draw_transformed(path, random(), random(), angle, growth[1])
    
    # Variations that decrement in size.
    # Colors are light with variations in hue.
    direction = choice((-1,1))
    for i in range(iterations):
        h = _wrap_around((clr.hue + random(tonality)*direction))
        s = clr.saturation + random(-0.2)
        b = random(0.2) + 0.8
        stroke(h, s, b, random(0.25))
        _draw_transformed(path, 1.5, 0, angle, growth[2])
    
    reset()
 
colors = ximport("colors")
colors.shadow(alpha=0.02, dx=30, dy=30)
 
colormode(RGB)
 clr = color(random(), random(), random())
nebula(clr)