Source

swirls / swirls.py

from __future__ import division

from math import cos, sin, pi
import pygame
from random import randrange, random
import sys
import time

WINDOW_SIZE = (640, 480)
WINDOW_CAPTION = 'Swirls'

class Object(object):
    pass

SINGULARITY = Object()
SINGULARITY.START_SPEED = 10
SINGULARITY.COUNT = 3
SINGULARITY.VISIBLE = False

FLARE = Object()
FLARE.START_SPEED = 80
FLARE.COUNT = 16
FLARE.MAX_SPEED = 10000
FLARE.FADE_TO_WHITE = True

GRAVITY_CONSTANT = 1000000.
NEUTRAL_DISTANCE = 20
HUE_CHANGE_RATE = 0.02
NUM_HEAD_SEGMENTS = 100
COOLING_FACTOR = 0.25
DIM_HALFLIFE = 30
WALL_DAMPING = 0.75
MAX_FRAME_RATE = 50

display = Object()
world = Object()

def main():
    initialise()
    while True:
        handle_events()
        advance_time()
        update_display()

def initialise():
    initialise_display()
    initialise_world()

def initialise_display():
    pygame.init()
    display.screen = pygame.display.set_mode(WINDOW_SIZE)
    pygame.display.set_caption(WINDOW_CAPTION)
    display.rect = pygame.Rect((0, 0), WINDOW_SIZE)

    display.canvas = pygame.Surface(WINDOW_SIZE)
    display.blank = pygame.Surface(WINDOW_SIZE)
    pygame.draw.rect(display.blank, (0, 0, 0), display.rect)
    display.dim_level = 1

def initialise_world():
    world.singularities = initialise_singularities()
    world.flares = initialise_flares()
    world.time = time.time()
    world.hue = random()
    world.mousedown = False

def initialise_flares():
    result = []
    for i in xrange(FLARE.COUNT):
        f = create_flare()
        result.append(f)
    return result

def create_flare():
    f = Object()
    f.last_pos = f.pos = centre_position()
    f.vel = random_start_velocity(FLARE)
    f.head_segments = []
    return f

def centre_position():
    width, height = WINDOW_SIZE
    return vector(width / 2, height / 2)

def initialise_singularities():
    result = []
    for i in xrange(SINGULARITY.COUNT):
        s = create_singularity()
        result.append(s)
    return result

def create_singularity():
    s = Object()
    s.pos = random_position()
    s.vel = random_start_velocity(SINGULARITY)
    return s

def random_position():
    width, height = WINDOW_SIZE
    return vector(
        randrange(width),
        randrange(height))

def vector(x, y):
    return [x, y]

def polar_vector(magnitude, angle):
    return [
        magnitude * cos(angle),
        magnitude * sin(angle)
    ]

def random_start_velocity(kind):
    angle = random() * 2 * pi
    magnitude = kind.START_SPEED
    return polar_vector(magnitude, angle)
    
def handle_events():
    for event in pygame.event.get():
        process_event(event)

def process_event(event):
    try:
        handler = EVENT_HANDLERS[event.type]
    except KeyError:
        return
    handler(event)

def process_quit_event(event):
    sys.exit(0)

def process_mousebuttondown_event(event):
    if event.button == 1:
        world.mousedown = True

def process_mousebuttonup_event(event):
    if event.button == 1:
        world.mousedown = False
    
EVENT_HANDLERS = {
    pygame.QUIT: process_quit_event,
    pygame.MOUSEBUTTONDOWN: process_mousebuttondown_event,
    pygame.MOUSEBUTTONUP: process_mousebuttonup_event,
}

def advance_time():
    now = time.time()
    delta = now - world.time
    if delta < 1 / MAX_FRAME_RATE:
        time.sleep(1/MAX_FRAME_RATE - delta)
        now = time.time()
        delta = now - world.time
    world.time = now
    colour = make_colour(world.hue)
    
    dim_canvas(delta)
    advance_hue(delta)
    for s in world.singularities:
        advance_singularity(s, delta)
    for f in world.flares:
        advance_flare(f, delta, colour)

def advance_hue(t):
    world.hue = (world.hue + t * HUE_CHANGE_RATE) % 1

def advance_flare(f, t, colour):
    acc = get_flare_acceleration(f)
    f.vel = add_vectors(f.vel, scalar_multiply(acc, t))
    f.vel = cap_magnitude(f.vel, FLARE.MAX_SPEED)
    advance_flare_to(f, add_vectors(f.pos, scalar_multiply(f.vel, t)), colour, t)
    if out_of_universe_left(f.pos):
        f.vel = make_rightwards(f.vel)
        f.vel = scalar_multiply(f.vel, WALL_DAMPING)
    elif out_of_universe_right(f.pos):
        f.vel = make_leftwards(f.vel)
        f.vel = scalar_multiply(f.vel, WALL_DAMPING)
    if out_of_universe_top(f.pos):
        f.vel = make_downwards(f.vel)
        f.vel = scalar_multiply(f.vel, WALL_DAMPING)
    elif out_of_universe_bottom(f.pos):
        f.vel = make_upwards(f.vel)
        f.vel = scalar_multiply(f.vel, WALL_DAMPING)

def advance_flare_to(f, new_pos, colour, t):
    f.last_pos = f.pos
    f.pos = new_pos
    for segment in f.head_segments:
        diminish_segment_temperature(segment, t)
    add_flare_head_segment(f, f.last_pos, f.pos, colour)

def diminish_segment_temperature(segment, t):
    segment.temperature *= (COOLING_FACTOR ** t)

def add_flare_head_segment(f, start, end, colour):
    s = Object()
    s.temperature = 1
    s.start = start
    s.end = end
    s.colour = colour
    f.head_segments.append(s)
    f.head_segments = f.head_segments[-NUM_HEAD_SEGMENTS:]

def cap_magnitude(vec, limit):
    m = vector_magnitude(vec)
    if m <= limit:
        return vec
    return scalar_multiply(vec, (limit / m))

def get_flare_acceleration(f):
    total = vector(0, 0)
    for s in world.singularities:
        a = get_acceleration_to(f, s.pos)
        total = add_vectors(total, a)
    if world.mousedown:
        a = get_acceleration_to(f, pygame.mouse.get_pos())
        a = scalar_multiply(a, 2)
        total = add_vectors(total, a)
    return total

def get_acceleration_to(f, pos):
    disp = subtract_vectors(pos, f.pos)
    dist = vector_magnitude(disp)

    if dist > NEUTRAL_DISTANCE:
        acc = GRAVITY_CONSTANT / (dist ** 2)
    else:
        acc = 0
    
    return scalar_multiply(disp, acc / dist)

def advance_singularity(s, t):
    s.pos = add_vectors(s.pos, scalar_multiply(s.vel, t))
    if out_of_window_left(s.pos):
        s.vel = make_rightwards(s.vel)
    elif out_of_window_right(s.pos):
        s.vel = make_leftwards(s.vel)
    if out_of_window_top(s.pos):
        s.vel = make_downwards(s.vel)
    elif out_of_window_bottom(s.pos):
        s.vel = make_upwards(s.vel)

def add_vectors(vec1, vec2):
    x1, y1 = vec1
    x2, y2 = vec2
    return vector(x1 + x2, y1 + y2)

def subtract_vectors(vec1, vec2):
    x1, y1 = vec1
    x2, y2 = vec2
    return vector(x1 - x2, y1 - y2)

def vector_magnitude(vec):
    x, y = vec
    return (x ** 2 + y ** 2) ** 0.5

def scalar_multiply(vec, t):
    x, y = vec
    return vector(x * t, y * t)

def out_of_window_left(pos):
    return pos[0] < 0

def out_of_window_right(pos):
    return pos[0] >= WINDOW_SIZE[0]

def out_of_window_top(pos):
    return pos[1] < 0

def out_of_window_bottom(pos):
    return pos[1] >= WINDOW_SIZE[1]

def out_of_universe_left(pos):
    return pos[0] < -WINDOW_SIZE[0]/2

def out_of_universe_right(pos):
    return pos[0] >= WINDOW_SIZE[0]*3/2

def out_of_universe_top(pos):
    return pos[1] < -WINDOW_SIZE[1]/2

def out_of_universe_bottom(pos):
    return pos[1] >= WINDOW_SIZE[1]*3/2

def make_rightwards(vel):
    x, y = vel
    return vector(abs(x), y)

def make_leftwards(vel):
    x, y = vel
    return vector(-abs(x), y)

def make_downwards(vel):
    x, y = vel
    return vector(x, abs(y))

def make_upwards(vel):
    x, y = vel
    return vector(x, -abs(y))

def update_display():
    clear_display()
    draw_world()
    show_display()

def clear_display():
    #pygame.draw.rect(display.screen, (0, 0, 0), display.rect)
    pass

def draw_world():
    draw_to_canvas()
    display.screen.blit(display.canvas, (0, 0))
    draw_temporaries()

def dim_canvas(t):
    display.dim_level *= 0.5 ** (t/DIM_HALFLIFE)
    alpha = 255 - int(256 * display.dim_level)
    if alpha > 0:
        display.blank.set_alpha(alpha)
        display.canvas.blit(display.blank, (0, 0))
        display.dim_level = 1

def draw_temporaries():
    for f in world.flares:
        draw_flare_head(f)
    for s in world.singularities:
        draw_singularity(s)

def draw_to_canvas():
    colour = make_colour(world.hue)
    for f in world.flares:
        draw_flare_trail(f, colour)

def draw_singularity(s):
    if SINGULARITY.VISIBLE:
        pygame.draw.circle(display.screen, (255, 128, 0),
            screen_position(s.pos), 5)

def draw_flare_trail(f, colour):
    pygame.draw.line(display.canvas, colour,
            screen_position(f.last_pos), screen_position(f.pos), 2)

def draw_flare_head(f):
    for segment in f.head_segments:
        draw_flare_head_segment(segment)

def draw_flare_head_segment(segment):
    colour = scale_segment_colour(segment.colour, segment.temperature)
    pygame.draw.line(display.screen, colour, screen_position(segment.start),
            screen_position(segment.end), 2)

def scale_segment_colour(base_colour, temperature):
    if FLARE.FADE_TO_WHITE:
        return scale_colour_towards_white(base_colour, temperature)
    return scale_colour_towards_black(base_colour, temperature)

def scale_colour_towards_white(base_colour, temperature):
    r, g, b = base_colour
    return (
        r + temperature * (255 - r),
        g + temperature * (255 - g),
        b + temperature * (255 - b),
    )

def scale_colour_towards_black(base_colour, temperature):
    r, g, b = base_colour
    return (
        r * (1 - temperature),
        g * (1 - temperature),
        b * (1 - temperature),
    )

def make_colour(hue):
    region, remainder = divmod(hue, 1/6)
    midbit = 6 * remainder
    if region == 0:
        r = (1, midbit, 0)
    elif region == 1:
        r = (1-midbit, 1, 0)
    elif region == 2:
        r = (0, 1, midbit)
    elif region == 3:
        r = (0, 1-midbit, 1)
    elif region == 4:
        r = (midbit, 0, 1)
    elif region == 5:
        r = (1, 0, 1-midbit)
    return tuple(int(255*i) for i in r)

def screen_position(pos):
    x, y = pos
    return (int(x), int(y))

def show_display():
    pygame.display.flip()

if __name__ == '__main__':
    main()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.