import pygame import pygame.gfxdraw from PIL import Image import random from math import cos, sin ##### CONFIG ##### window_size = (1280, 720) fps = 60 # pixel size in percentage of window height (0.0 - 1.0) pixel_size = 0.018 # min and max distance from screen edge to pixel in window height percentage (0.0 - 1.0) min_distribution_edge_dist = 0.1 max_distribution_edge_dist = 0.2 initial_vel_randomness = 1 # max random delay of pixel simulation start in seconds max_random_delay = 2 ## simulation settings # 0.0 - inf stiffness = 40 # 0.0 - 1.0 damping = 0.08 ################## damping_multiplier = 1 - damping target_delta_time = 1 / fps half_window_size = (window_size[0] / 2, window_size[1] / 2) print("=====================") aspect_ratio = window_size[0] / window_size[1] half_aspect_ratio = aspect_ratio / 2 print(f"Aspect ratio: {aspect_ratio}") pixel_size_px = pixel_size * window_size[1] half_pixel_size_px = round(pixel_size_px / 2) print(f"Pixel size: {pixel_size_px}px") # import logo image logo = Image.open("logo_new.png").convert("1") logo_size = (logo.size[0] * pixel_size, logo.size[1] * pixel_size) print(f"Logo size: {logo_size}") # offset in world space so the logo is in the center #logo_offset = ((aspect_ratio - logo_size[0]) / 2, (1 - logo_size[1]) / 2) logo_offset = (logo_size[0] / 2, logo_size[1] / 2) print(f"Logo offset: {logo_offset}") print("=====================") pixels_target_pos = [] for y in range(logo.size[1]): for x in range(logo.size[0]): if logo.getpixel((x, y)) == 255: pixels_target_pos.append((x * pixel_size - logo_offset[0], y * pixel_size - logo_offset[1])) pixels_target_pos = tuple(pixels_target_pos) ## distribute pixels in random positions off the screen pixels_pos_and_vel = [] for pixel in pixels_target_pos: # select random screen edge axis using 1 and aspect_ratio as weights | 0 = x, 1 = y edge_axis = random.choices((0, 1), weights=[1, aspect_ratio])[0] # select random edge | (x axis): 0 = left, 1 = right | (y axis): 0 = top, 1 = bottom edge = random.choice((-1, 1)) # random position on edge if edge_axis == 0: # x axis (vertical edges) pos = [half_aspect_ratio * edge, random.uniform(-.5, .5)] else: # y axis (horizontal edges) pos = [random.uniform(-half_aspect_ratio, half_aspect_ratio), edge / 2] # offset pos by random 0 to distribution_edge_dist_px pos[edge_axis] += random.uniform(min_distribution_edge_dist, max_distribution_edge_dist) * edge # pos, velocity, delay pixels_pos_and_vel.append([ pos, [random.uniform(-initial_vel_randomness, initial_vel_randomness), random.uniform(-initial_vel_randomness, initial_vel_randomness)], random.uniform(0, max_random_delay) + 1 ]) # pygame init pygame.init() window = pygame.display.set_mode(window_size) clock = pygame.time.Clock() rot_zoom_delay = 6 rot_speed_increase = 0.2 zoom_speed_increase = 0.65 rot_speed = 0 zoom_speed = 0.04 fade_delay = 1.5 fade_speed = 255/1.5 angle = 0 zoom = 1 fade_val = 255 can_rot = False # main loop while True: # events for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() quit() window.fill((0, 0, 0)) if fade_val > 0: if rot_zoom_delay > 0: rot_zoom_delay -= target_delta_time else: # zooming more and rotating rot_speed += rot_speed_increase * target_delta_time zoom_speed += zoom_speed_increase * target_delta_time angle += rot_speed * target_delta_time # rotate and zoom rot_cos = cos(angle) rot_sin = sin(angle) if not can_rot: can_rot = True # fade if fade_delay > 0: fade_delay -= target_delta_time else: fade_val -= fade_speed * target_delta_time if fade_val < 0: continue zoom *= 1 + zoom_speed * target_delta_time # calculate pixel points positions scaled_pixel_size_px = pixel_size_px * zoom half_scaled_pixel_size_px = round(scaled_pixel_size_px / 2) scaled_pixel_size_px = round(scaled_pixel_size_px) pixel_points = ( (0, 0), (scaled_pixel_size_px, 0), (scaled_pixel_size_px, scaled_pixel_size_px), (0, scaled_pixel_size_px) ) pixel_points = tuple( ( pixel_point[0] - half_scaled_pixel_size_px, pixel_point[1] - half_scaled_pixel_size_px ) for pixel_point in pixel_points ) # rotate pixel points if can_rot: pixel_points = tuple( ( pixel_point[0] * rot_cos - pixel_point[1] * rot_sin, pixel_point[0] * rot_sin + pixel_point[1] * rot_cos ) for pixel_point in pixel_points ) # draw pixels for pixel_idx, pixel in enumerate(pixels_pos_and_vel): # check if delay is over if pixel[2] > 0: pixel[2] -= target_delta_time continue ## simulation (take target_delta_time into account) # get pixel pos and vel pos = pixel[0] vel = pixel[1] # delta pos (target pos - current pos) delta_pos = [pixels_target_pos[pixel_idx][0] - pos[0], pixels_target_pos[pixel_idx][1] - pos[1]] # update velocity vel[0] += delta_pos[0] * stiffness * target_delta_time vel[1] += delta_pos[1] * stiffness * target_delta_time # damping vel[0] *= damping_multiplier vel[1] *= damping_multiplier # update pos pos[0] += vel[0] * target_delta_time pos[1] += vel[1] * target_delta_time # update pixel pos and vel pixels_pos_and_vel[pixel_idx][0:2] = [pos, vel] ## draw pixel # rotate if can_rot: pixel_draw_pos = ( pos[0] * rot_cos - pos[1] * rot_sin, pos[0] * rot_sin + pos[1] * rot_cos ) else: pixel_draw_pos = pos # zoom perceived_zoom = zoom pixel_draw_pos = (pixel_draw_pos[0] * perceived_zoom, pixel_draw_pos[1] * perceived_zoom) # convert to screen space pixel_draw_pos = ( pixel_draw_pos[0] * window_size[1] + half_window_size[0], pixel_draw_pos[1] * window_size[1] + half_window_size[1] ) # DEBUG draw sphere #pygame.draw.circle(window, (255, 255, 255), (round(pixel_draw_pos[0]), round(pixel_draw_pos[1])), 200, 5) # DEBUG draw dot #pygame.draw.circle(window, (255, 0, 0), (round(pixel_draw_pos[0]), round(pixel_draw_pos[1])), 1) # draw pixel points = tuple( (round(pixel_draw_pos[0] + pixel_point[0]), round(pixel_draw_pos[1] + pixel_point[1])) for pixel_point in pixel_points ) pygame.gfxdraw.aapolygon( window, points, (fade_val,)*3 ) pygame.gfxdraw.filled_polygon( window, points, (fade_val,)*3 ) # update pygame.display.update() clock.tick(60)