예제 #1
0
def pack(circles, x, y, padding=2, exclude=[]):
    """ Circle-packing algorithm.
        Groups the given list of Circle objects around (x,y) in an organic way.
    """
    # Ported from Sean McCullough's Processing code:
    # http://www.cricketschirping.com/processing/CirclePacking1/
    # See also: http://en.wiki.mcneel.com/default.aspx/McNeel/2DCirclePacking

    # Repulsive force: move away from intersecting circles.
    for i, circle1 in enumerate(circles):
        for circle2 in circles[i + 1:]:
            d = distance(circle1.x, circle1.y, circle2.x, circle2.y)
            r = circle1.radius + circle2.radius + padding
            if d < r - 0.01:
                dx = circle2.x - circle1.x
                dy = circle2.y - circle1.y
                vx = (dx / d) * (r - d) * 0.5
                vy = (dy / d) * (r - d) * 0.5
                if circle1 not in exclude:
                    circle1.x -= vx
                    circle1.y -= vy
                if circle2 not in exclude:
                    circle2.x += vx
                    circle2.y += vy

    # Attractive force: move all circles to center.
    for circle in circles:
        circle.goal.x = x
        circle.goal.y = y
        if circle not in exclude:
            damping = circle.radius**3 * 0.000001  # Big ones in the middle.
            vx = (circle.x - x) * damping
            vy = (circle.y - y) * damping
            circle.x -= vx
            circle.y -= vy
예제 #2
0
def pack(circles, x, y, padding=2, exclude=[]):
    """ Circle-packing algorithm.
        Groups the given list of Circle objects around (x,y) in an organic way.
    """
    # Ported from Sean McCullough's Processing code:
    # http://www.cricketschirping.com/processing/CirclePacking1/
    # See also: http://en.wiki.mcneel.com/default.aspx/McNeel/2DCirclePacking
    
    # Repulsive force: move away from intersecting circles.
    for i, circle1 in enumerate(circles):
        for circle2 in circles[i+1:]:
            d = distance(circle1.x, circle1.y, circle2.x, circle2.y)
            r = circle1.radius + circle2.radius + padding
            if d < r - 0.01:
                dx = circle2.x - circle1.x
                dy = circle2.y - circle1.y
                vx = (dx / d) * (r-d) * 0.5
                vy = (dy / d) * (r-d) * 0.5
                if circle1 not in exclude:
                    circle1.x -= vx
                    circle1.y -= vy
                if circle2 not in exclude:
                    circle2.x += vx
                    circle2.y += vy
    
    # Attractive force: move all circles to center.
    for circle in circles:
        circle.goal.x = x
        circle.goal.y = y
        if circle not in exclude:
            damping = circle.radius ** 3 * 0.000001 # Big ones in the middle.
            vx = (circle.x - x) * damping
            vy = (circle.y - y) * damping
            circle.x -= vx
            circle.y -= vy
예제 #3
0
def spider(string, x=0, y=0, radius=25, **kwargs):
    """ A path filter that creates web threading along the characters of the given string.
        Its output can be drawn directly to the canvas or used in a render() function.
        Adapted from: http://nodebox.net/code/index.php/Path_Filters
    """
    # **kwargs represents any additional optional parameters.
    # For example: spider("hello", 100, 100, font="Helvetica") =>
    # kwargs = {"font": "Helvetica"}
    # We pass these on to the textpath() call in the function; 
    # so the spider() function takes the same parameters as textpath: 
    # x, y, font, fontsize, fontweight, ...
    font(
        kwargs.get("font", "Droid Sans"),
        kwargs.get("fontsize", 100))
    p = textpath(string, x, y, **kwargs)
    n = int(p.length)
    m = 2.0
    radius = max(radius, 0.1 * fontsize())
    points = list(p.points(n))
    for i in range(n):
        pt1 = choice(points)
        pt2 = choice(points)
        while distance(pt1.x, pt1.y, pt2.x, pt2.y) > radius:
            pt2  = choice(points)      
        line(pt1.x + random(-m, m), 
             pt1.y + random(-m, m),
             pt2.x + random(-m, m), 
             pt2.y + random(-m, m))
예제 #4
0
 def draw_mesh(self, points):
     strokewidth(0.1)
     stroke(1, 0, 0.4, 0.6)
     fill(1, 0, 0.4, 0.3)
     for i, (p1, dx1, dy1, a1) in enumerate(points):
         # Draw feeler.
         line(p1.x, p1.y, p1.x+dx1, p1.y+dy1)
         ellipse(p1.x+dx1, p1.y+dy1, 1.5, 1.5)
         ellipse(p1.x, p1.y, 1, 1)
     stroke(0.8,0.9,1, 0.1)
     for i, (p1, dx1, dy1, a1) in enumerate(points):
         # Draw connection to nearest-neighbor particle.
         nn, d0 = None, None
         for p2, dx2, dy2, a2 in points:
             d = distance(p1.x, p1.y, p2.x, p2.y)
             if p1 != p2 and (d0 is None or d < d0):
                 nn, d0 = p2, d
         if nn is not None:
             line(p1.x, p1.y, nn.x, nn.y)
     nostroke()
예제 #5
0
 def draw_mesh(self, points):
     strokewidth(0.1)
     stroke(1, 0, 0.4, 0.6)
     fill(1, 0, 0.4, 0.3)
     for i, (p1, dx1, dy1, a1) in enumerate(points):
         # Draw feeler.
         line(p1.x, p1.y, p1.x + dx1, p1.y + dy1)
         ellipse(p1.x + dx1, p1.y + dy1, 1.5, 1.5)
         ellipse(p1.x, p1.y, 1, 1)
     stroke(0.8, 0.9, 1, 0.1)
     for i, (p1, dx1, dy1, a1) in enumerate(points):
         # Draw connection to nearest-neighbor particle.
         nn, d0 = None, None
         for p2, dx2, dy2, a2 in points:
             d = distance(p1.x, p1.y, p2.x, p2.y)
             if p1 != p2 and (d0 is None or d < d0):
                 nn, d0 = p2, d
         if nn is not None:
             line(p1.x, p1.y, nn.x, nn.y)
     nostroke()
예제 #6
0
 def update(self):
     """ Attractor roams around and sucks in particles
     """
     Particle.update(self)
     
     # Attractor wants to be in the center of the canvas.
     # This urge increases as its gravity (i.e., number of attached particles) increases.
     vx = self.x - canvas.width/2
     vy = self.y - canvas.height/2
     f = 0.0015 * self.gravity**2
     self.x -= vx * f
     self.y -= vy * f
     
     # Attractive force: move all particles to attractor.
     for p in self.particles:
         #p.v.angle = 0#angle(p.x, p.y, self.x, self.y) # Point to attractor.
         f = p.radius * 0.004
         vx = (p.x - self.x) * f
         vy = (p.y - self.y) * f
         p.v.x = -vx
         p.v.y = -vy
         
     # Repulsive force: move away from intersecting particles.
     for i, p1 in enumerate(self.particles):
         for p2 in self.particles[i+1:] + [self]:
             d = distance(p1.x, p1.y, p2.x, p2.y)
             r = p1.radius + p2.radius
             f = 0.15
             if d < r - 0.01:
                 dx = p2.x - p1.x
                 dy = p2.y - p1.y
                 vx = (dx / d) * (r-d) * f
                 vy = (dy / d) * (r-d) * f
                 if p1 != self:
                     p1.v.x -= vx
                     p1.v.y -= vy
                 if p2 != self:
                     p2.v.x += vx
                     p2.v.y += vy
예제 #7
0
    def update(self):
        """ Attractor roams around and sucks in particles
        """
        Particle.update(self)

        # Attractor wants to be in the center of the canvas.
        # This urge increases as its gravity (i.e., number of attached particles) increases.
        vx = self.x - canvas.width / 2
        vy = self.y - canvas.height / 2
        f = 0.0015 * self.gravity**2
        self.x -= vx * f
        self.y -= vy * f

        # Attractive force: move all particles to attractor.
        for p in self.particles:
            #p.v.angle = 0#angle(p.x, p.y, self.x, self.y) # Point to attractor.
            f = p.radius * 0.004
            vx = (p.x - self.x) * f
            vy = (p.y - self.y) * f
            p.v.x = -vx
            p.v.y = -vy

        # Repulsive force: move away from intersecting particles.
        for i, p1 in enumerate(self.particles):
            for p2 in self.particles[i + 1:] + [self]:
                d = distance(p1.x, p1.y, p2.x, p2.y)
                r = p1.radius + p2.radius
                f = 0.15
                if d < r - 0.01:
                    dx = p2.x - p1.x
                    dy = p2.y - p1.y
                    vx = (dx / d) * (r - d) * f
                    vy = (dy / d) * (r - d) * f
                    if p1 != self:
                        p1.v.x -= vx
                        p1.v.y -= vy
                    if p2 != self:
                        p2.v.x += vx
                        p2.v.y += vy
예제 #8
0
 def contains(self, x, y):
     return distance(self.x, self.y, x, y) <= self.radius
예제 #9
0
def draw(canvas):
    global headset
    global dimmer
    global images
    global samples
    global particles
    global attractor
    global ZOOM, ATTRACT, SPAWN, DIM, delay
    global MUTE
    
    glEnable(GL_DITHER)
    
    background(0)
    #image(abspath("g","bg.png"), 0, 0, width=canvas.width, height=canvas.height)
    image(abspath("g","bg-light.png"), 0, 0, width=canvas.width, height=canvas.height, alpha=0.9)
    
    if canvas.key.code == SPACE:
        MUTE = not MUTE

    # Poll the headset.
    # Is alpha above average? => attraction.
    # Is valence above average? => spawn feelies.
    headset.update(buffer=1024)
    ATTRACT = False
    ATTRACT = delay > 0
    ATTRACT = ATTRACT or SHIFT in canvas.key.modifiers
    if canvas.key.code == SHIFT:
        ATTRACT = True
    if len(headset.alpha[0]) > 0 and headset.alpha[0][-1][0] > headset.alpha[0][-1][1] * 1.0:
        ATTRACT = True
        delay = 10 # Delay before repulsing to counter small alpha fluctuation.
    elif delay > 0:
        delay -= 1
    SPAWN = False
    SPAWN = CTRL in canvas.key.modifiers
    if canvas.key.code == CTRL:
        SPAWN = True
    if len(headset.valence) > 0 and headset.valence[-1][0] > headset.valence[-1][1]:
        SPAWN = True
    
    # In mute mode, ignore triggering alpha and valence.
    if MUTE:
        ATTRACT = SPAWN = False
        delay = 0
    
    # Dimmer sends a value over UDP that drops to 0 when relaxed.
    # It can be used to dim ambient lighting using a domotica module.
    m = 0.0025
    if ATTRACT:
        DIM = clamp(DIM-m, 0.0, 1.0)
    else:
        DIM = clamp(DIM+m, 0.0, 1.0)
    if DIM < 0.8 and dimmer is not None:
        dimmer.send("%.2f" % (DIM * 100))
    
    # Valence controls the balance between high and low ambient.
    v = headset.valence.slope # -1.0 => +1.0
    v = 0.0
    dx = 1.0 - v
    dy = 1.0 + v
    # Mouse changes the volume of low and high ambient sound.
    #dx = canvas.mouse.relative_x
    #dy = canvas.mouse.relative_y
    samples["ambient_lo"].play(volume=0.7 * dx)
    samples["ambient_hi"].play(volume=0.7 * dy)

    if canvas.key.code == ALT:
        text("%.2f FPS" % canvas.profiler.framerate, canvas.width-80, 15, align=RIGHT, fill=[1,1,1,0.75])

    if canvas.frame / 20 % 2 == 0:
        fill(1,1,1, 0.75)
        fontsize(9)
        if len(headset.alpha[0]) > 0:
            ellipse(canvas.width-18, 19.5, 7, 7, fill=[1,1,1,1])
        if ATTRACT or SPAWN:
            ellipse(15, 19.5, 7, 7, fill=[1,0,0,1])
        if ATTRACT and SPAWN:
            text(" RELAXATION + AROUSAL", 20, 15)
        elif ATTRACT:
            text(" RELAXATION", 20, 15)
        elif SPAWN:
            text(" AROUSAL", 20, 15)
        elif MUTE:
            text(" READY", 20, 15)

    # Zoom out as the attractor grows larger.
    # Integrate the zoom scale to make the transition smoother.
    d = (1.25 - len(attractor.particles) * 0.05)
    if ZOOM > -0.15 and ZOOM > d:
        ZOOM -= 0.0025
    if ZOOM < +1.25 and ZOOM < d:
        ZOOM += 0.0025   
    dx = 0.5 * ZOOM * canvas.width
    dy = 0.5 * ZOOM * canvas.height
    translate(-dx, -dy)
    scale(1.0 + ZOOM)

    for p in list(particles):
        d = distance(p.x, p.y, attractor.x, attractor.y)
        t = d / canvas.width * 2
        p.update()
        # When valence is low, unattached feelie particles fade away.
        if SPAWN is False:
            if p.parent is None and p.type == FEELIE:
                p.alpha -= 0.04
                p.alpha = max(p.alpha, 0)
                if p.alpha == 0:
                    # Remove hidden feelies, so we have a chance to see new ones.
                    particles.remove(p)
        # Check if a particle falls within the attraction radius:
        # If so, attract it when alpha is above average.
        if ATTRACT is True:
            if p.parent is None and p.frames >= 0 and p.alpha >= 0.25:
                if d < min(210, p.radius + attractor.radius * attractor.gravity):
                    attractor.append(p)
                    samples["attract"].play().volume = 0.75
        p.draw(blur=t, alpha=(1-t))
                    
    # Repulse when alpha drops below average.
    # Press mouse to repulse attracted particles.
    if ATTRACT is False:
        if random() > 0.5:
            if len(attractor.particles) > 0:
                attractor.remove(attractor.particles[0])
                samples["repulse"].play()
                
    # When valence is high, feelie particles appear.
    if SPAWN is True: 
        if random() > 0.5:
            if len(particles) < 80:
                p = Particle(x = choice((-30, canvas.width+30)),
                             y = -30,
                         image = choice([images["flower%i.png"%i] for i in range(2,6+1)], bias=0.25),
                        radius = 15 + random(20),
                        bounds = (-65, -65, canvas.width+65, canvas.height+65),
                         speed = 3.5,
                          type = FEELIE)
                if p.image._src[0].endswith("flower3.png"):
                    p.radius = 20 + random(20)
                if p.image._src[0].endswith("flower4.png"):
                    p.radius = 15 + random(10)
                if p.image._src[0].endswith("flower5.png"):
                    p.radius = 15
                if p.image._src[0].endswith("flower6.png"):
                    p.radius = 10 + random(5)
                particles.append(p)

    attractor.update()
    attractor.draw_halo()
    attractor.draw()
예제 #10
0
 def contains(self, x, y):
     return distance(self.x, self.y, x, y) <= self.radius
예제 #11
0
def draw(canvas):
    global headset
    global dimmer
    global images
    global samples
    global particles
    global attractor
    global ZOOM, ATTRACT, SPAWN, DIM, delay
    global MUTE

    glEnable(GL_DITHER)

    background(0)
    #image(abspath("g","bg.png"), 0, 0, width=canvas.width, height=canvas.height)
    image(abspath("g", "bg-light.png"),
          0,
          0,
          width=canvas.width,
          height=canvas.height,
          alpha=0.9)

    if canvas.key.code == SPACE:
        MUTE = not MUTE

    # Poll the headset.
    # Is alpha above average? => attraction.
    # Is valence above average? => spawn feelies.
    headset.update(buffer=1024)
    ATTRACT = False
    ATTRACT = delay > 0
    ATTRACT = ATTRACT or SHIFT in canvas.key.modifiers
    if canvas.key.code == SHIFT:
        ATTRACT = True
    if len(headset.alpha[0]
           ) > 0 and headset.alpha[0][-1][0] > headset.alpha[0][-1][1] * 1.0:
        ATTRACT = True
        delay = 10  # Delay before repulsing to counter small alpha fluctuation.
    elif delay > 0:
        delay -= 1
    SPAWN = False
    SPAWN = CTRL in canvas.key.modifiers
    if canvas.key.code == CTRL:
        SPAWN = True
    if len(headset.valence
           ) > 0 and headset.valence[-1][0] > headset.valence[-1][1]:
        SPAWN = True

    # In mute mode, ignore triggering alpha and valence.
    if MUTE:
        ATTRACT = SPAWN = False
        delay = 0

    # Dimmer sends a value over UDP that drops to 0 when relaxed.
    # It can be used to dim ambient lighting using a domotica module.
    m = 0.0025
    if ATTRACT:
        DIM = clamp(DIM - m, 0.0, 1.0)
    else:
        DIM = clamp(DIM + m, 0.0, 1.0)
    if DIM < 0.8 and dimmer is not None:
        dimmer.send("%.2f" % (DIM * 100))

    # Valence controls the balance between high and low ambient.
    v = headset.valence.slope  # -1.0 => +1.0
    v = 0.0
    dx = 1.0 - v
    dy = 1.0 + v
    # Mouse changes the volume of low and high ambient sound.
    #dx = canvas.mouse.relative_x
    #dy = canvas.mouse.relative_y
    samples["ambient_lo"].play(volume=0.7 * dx)
    samples["ambient_hi"].play(volume=0.7 * dy)

    if canvas.key.code == ALT:
        text("%.2f FPS" % canvas.profiler.framerate,
             canvas.width - 80,
             15,
             align=RIGHT,
             fill=[1, 1, 1, 0.75])

    if canvas.frame / 20 % 2 == 0:
        fill(1, 1, 1, 0.75)
        fontsize(9)
        if len(headset.alpha[0]) > 0:
            ellipse(canvas.width - 18, 19.5, 7, 7, fill=[1, 1, 1, 1])
        if ATTRACT or SPAWN:
            ellipse(15, 19.5, 7, 7, fill=[1, 0, 0, 1])
        if ATTRACT and SPAWN:
            text(" RELAXATION + AROUSAL", 20, 15)
        elif ATTRACT:
            text(" RELAXATION", 20, 15)
        elif SPAWN:
            text(" AROUSAL", 20, 15)
        elif MUTE:
            text(" READY", 20, 15)

    # Zoom out as the attractor grows larger.
    # Integrate the zoom scale to make the transition smoother.
    d = (1.25 - len(attractor.particles) * 0.05)
    if ZOOM > -0.15 and ZOOM > d:
        ZOOM -= 0.0025
    if ZOOM < +1.25 and ZOOM < d:
        ZOOM += 0.0025
    dx = 0.5 * ZOOM * canvas.width
    dy = 0.5 * ZOOM * canvas.height
    translate(-dx, -dy)
    scale(1.0 + ZOOM)

    for p in list(particles):
        d = distance(p.x, p.y, attractor.x, attractor.y)
        t = d / canvas.width * 2
        p.update()
        # When valence is low, unattached feelie particles fade away.
        if SPAWN is False:
            if p.parent is None and p.type == FEELIE:
                p.alpha -= 0.04
                p.alpha = max(p.alpha, 0)
                if p.alpha == 0:
                    # Remove hidden feelies, so we have a chance to see new ones.
                    particles.remove(p)
        # Check if a particle falls within the attraction radius:
        # If so, attract it when alpha is above average.
        if ATTRACT is True:
            if p.parent is None and p.frames >= 0 and p.alpha >= 0.25:
                if d < min(210,
                           p.radius + attractor.radius * attractor.gravity):
                    attractor.append(p)
                    samples["attract"].play().volume = 0.75
        p.draw(blur=t, alpha=(1 - t))

    # Repulse when alpha drops below average.
    # Press mouse to repulse attracted particles.
    if ATTRACT is False:
        if random() > 0.5:
            if len(attractor.particles) > 0:
                attractor.remove(attractor.particles[0])
                samples["repulse"].play()

    # When valence is high, feelie particles appear.
    if SPAWN is True:
        if random() > 0.5:
            if len(particles) < 80:
                p = Particle(
                    x=choice((-30, canvas.width + 30)),
                    y=-30,
                    image=choice(
                        [images["flower%i.png" % i] for i in range(2, 6 + 1)],
                        bias=0.25),
                    radius=15 + random(20),
                    bounds=(-65, -65, canvas.width + 65, canvas.height + 65),
                    speed=3.5,
                    type=FEELIE)
                if p.image._src[0].endswith("flower3.png"):
                    p.radius = 20 + random(20)
                if p.image._src[0].endswith("flower4.png"):
                    p.radius = 15 + random(10)
                if p.image._src[0].endswith("flower5.png"):
                    p.radius = 15
                if p.image._src[0].endswith("flower6.png"):
                    p.radius = 10 + random(5)
                particles.append(p)

    attractor.update()
    attractor.draw_halo()
    attractor.draw()