-
-
Save hx2A/1b717cf958932a97a06a860d83c2fd85 to your computer and use it in GitHub Desktop.
py5 source code for 2020 Holiday Animation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy as np | |
import py5 | |
np.random.seed(42) | |
Camera3D = py5.JClass('camera3D.Camera3D') | |
CARD_WIDTH = 1795 | |
CARD_HEIGHT = 1287 | |
SIZE_FACTOR = 0.75 | |
# SIZE_FACTOR = 1.0 | |
NEAR = 1025.0 | |
FAR = 1525.0 | |
SNOWFLAKE_SIZE = 60 | |
SNOWFLAKE_LETTERS = "ABDIKLMNOPSUZqrstvxyz123457+-()" | |
SNOWFLAKE_PROBABILITY = 0.12 * SIZE_FACTOR | |
SNOWFLAKE_DELTA_X_SCALE = 3 | |
SNOWFLAKE_DELTA_Y_SCALE = 1.5 | |
SNOWFLAKE_Y_AVG_SPEED = 1.5 | |
SNOWFLAKE_DELTA_ROT_SCALE = 0.05 | |
SNOWFLAKE_NOISE_SCALE = 400 | |
SNOWFLAKE_Y_START_RANGE = 250 | |
TREE_LEVELS = 13 | |
TREE_LEVEL_HEIGHT = 40 | |
TREE_RADIUS_RATIO = 18 | |
TREE_BOUNDARY = 0.5 | |
TREE_BLINK = True | |
ANIMATION_START = 5 | |
ANIMATION_LENGTH = 65 | |
ANIMATION_SNOWFLAKE_LENGTH = 35 | |
ANIMATION_FRAME_RATE = 30 | |
class Figure: | |
def __init__(self, trans_args, rotate_args, img_args, scale=None): | |
self.z = trans_args[2] | |
self.trans_args = trans_args | |
self.rotate_args = rotate_args | |
self.img_args = img_args | |
self.scale = scale | |
def draw(self): | |
py5.push_matrix() | |
py5.translate(*self.trans_args) | |
py5.rotate_z(*self.rotate_args) | |
if self.scale: | |
py5.scale(self.scale) | |
py5.image(*self.img_args) | |
py5.pop_matrix() | |
class Tree: | |
def __init__(self, xpos, ypos, zpos): | |
self.xpos = xpos | |
self.ypos = ypos | |
self.zpos = zpos | |
self.star_img = py5.load_image('images/star.png') | |
self.images = [] | |
self.images.append(py5.load_image('images/bell.png')) | |
self.images.append(py5.load_image('images/candy_cane.png')) | |
self.images.append(py5.load_image('images/leaves.png')) | |
self.images.append(py5.load_image('images/candy_cane.png')) | |
self.images.append(py5.load_image('images/ornament.png')) | |
self.images.append(py5.load_image('images/candy_cane.png')) | |
self.images.append(py5.load_image('images/snowman.png')) | |
def check_collision(self, x, y, z): | |
tree_level = (y - self.ypos) / TREE_LEVEL_HEIGHT | |
if tree_level > TREE_LEVELS: | |
return 0 | |
tree_radius = 1.1 * TREE_RADIUS_RATIO * tree_level | |
distance = ((x - self.xpos)**2 + (z - self.zpos)**2)**0.5 | |
if distance < (1 + TREE_BOUNDARY) * tree_radius: | |
return 5 * np.sign(x - self.xpos) * (1 - (distance - tree_radius) / (TREE_BOUNDARY * tree_radius)) | |
return 0 | |
def get_figures(self): | |
figures = [] | |
trans_args = self.xpos, self.ypos - 10, self.zpos | |
rotate_args = (0,) | |
img_args = self.star_img, 0, 0, 60, 60 | |
figures.append(Figure(trans_args, rotate_args, img_args)) | |
for tree_level in range(1, TREE_LEVELS + 1): | |
radius = TREE_RADIUS_RATIO * tree_level | |
circumference = 2 * py5.PI * radius | |
count = int(circumference / 40) | |
for i in range(count): | |
theta = py5.TWO_PI * i / count + (tree_level % 2 * 2 - 1) * py5.frame_count / 1000 | |
rand_val = py5.noise(tree_level + py5.frame_count / 1000, 4 * theta) | |
img_index = int(py5.noise(tree_level, i) * 1000) % len(self.images) | |
rotate_sgn = (int(py5.noise(tree_level**2, i**2) * 1000) % 2) * 2 - 1 | |
if not TREE_BLINK or np.abs(rand_val) < 0.4: | |
xpos = self.xpos + radius * np.sin(theta) | |
ypos = self.ypos + TREE_LEVEL_HEIGHT * tree_level | |
zpos = self.zpos + radius * np.cos(theta) | |
trans_args = xpos, ypos, zpos | |
rotate_args = (0.2 * rotate_sgn,) | |
img_args = self.images[img_index], 0, 0, 30, 30 | |
figures.append(Figure(trans_args, rotate_args, img_args)) | |
return figures | |
class Snowflake: | |
def __init__(self, init=False): | |
self.char = np.random.choice(list(SNOWFLAKE_LETTERS)) | |
self.rad = 0 | |
self.x_salt = 10000 * np.random.rand() | |
self.y_salt = 10000 * np.random.rand() | |
self.x = py5.width * (2 * np.random.rand() - 1) | |
self.y = -(py5.height - 450 + SNOWFLAKE_Y_START_RANGE * (not init or np.random.rand())) | |
self.z = -500 * np.random.rand() | |
self.scale = 1 | |
def update(self, tree): | |
self.x += SNOWFLAKE_DELTA_X_SCALE * py5.noise( | |
self.x / SNOWFLAKE_NOISE_SCALE + self.x_salt, | |
self.y / SNOWFLAKE_NOISE_SCALE + self.y_salt, | |
py5.frame_count / SNOWFLAKE_NOISE_SCALE) | |
self.y += SNOWFLAKE_Y_AVG_SPEED + SNOWFLAKE_DELTA_Y_SCALE * py5.noise( | |
self.x / SNOWFLAKE_NOISE_SCALE + self.x_salt, | |
self.y / SNOWFLAKE_NOISE_SCALE + self.y_salt, | |
py5.frame_count / SNOWFLAKE_NOISE_SCALE + 500) | |
self.rad += SNOWFLAKE_DELTA_ROT_SCALE * py5.noise( | |
self.x / SNOWFLAKE_NOISE_SCALE + self.x_salt, | |
self.y / SNOWFLAKE_NOISE_SCALE + self.y_salt, | |
py5.frame_count / SNOWFLAKE_NOISE_SCALE + 1000) | |
self.x += tree.check_collision(self.x, self.y, self.z) | |
def draw(self): | |
py5.push_matrix() | |
py5.translate(self.x, self.y, self.z) | |
py5.rotate(self.rad) | |
py5.scale(self.scale) | |
py5.text(self.char, 0, SNOWFLAKE_SIZE / 2) | |
py5.pop_matrix() | |
def is_alive(self): | |
return self.y < py5.height + 50 | |
landscape = [] | |
tree = None | |
snowflakes = [] | |
def settings(): | |
py5.size(int(CARD_WIDTH * SIZE_FACTOR), int(CARD_HEIGHT * SIZE_FACTOR), py5.P3D) | |
def setup(): | |
global landscape | |
landscape = [py5.load_image(f'images/landscape_{i}.png') for i in [2, 3]] | |
camera3D = Camera3D(py5.get_current_sketch()) | |
camera3D.renderChromaDepth().setNearFar(NEAR, FAR).setTextureShader() | |
camera3D.camera(0, 0, 1000, 0, 0, 0, 0, 1, 0) | |
camera_z = (py5.height / 2) / py5.tan(py5.PI / 6) | |
camera3D.perspective(py5.PI / 4.5, py5.width / py5.height, camera_z / 10, camera_z * 10) | |
camera3D.setBackgroundColor(py5.color(0, 0, 64)) | |
snowflake_font = py5.create_font('wwflakes.ttf', SNOWFLAKE_SIZE) | |
global tree | |
tree = Tree(0, -250, -250) | |
py5.text_font(snowflake_font) | |
py5.text_align(py5.CENTER, py5.BOTTOM) | |
py5.image_mode(py5.CENTER) | |
initial_snowflake_count = int(SNOWFLAKE_Y_START_RANGE / SNOWFLAKE_Y_AVG_SPEED * SNOWFLAKE_PROBABILITY) | |
for _ in range(initial_snowflake_count): | |
snowflakes.append(Snowflake(init=True)) | |
def draw(): | |
figures = [] | |
## Landscape | |
figures.append(Figure((0, 200, -475), (0,), (landscape[0], 0, 0), scale=0.9)) | |
figures.append(Figure((0, 0, -1200), (0,), (landscape[1], 0, 0), scale=1.35)) | |
### Christmas Tree | |
figures.extend(tree.get_figures()) | |
### Snowflakes | |
py5.no_stroke() | |
global snowflakes | |
for s in snowflakes: | |
s.update(tree) | |
snowflakes = list(filter(lambda s: s.is_alive(), snowflakes)) | |
for s in snowflakes: | |
figures.append(s) | |
for fig in sorted(figures, key=lambda fig: fig.z): | |
fig.draw() | |
if py5.frame_count < (ANIMATION_START + ANIMATION_SNOWFLAKE_LENGTH) * ANIMATION_FRAME_RATE: | |
if np.random.rand() < SNOWFLAKE_PROBABILITY: | |
snowflakes.append(Snowflake()) | |
if py5.frame_count > ANIMATION_START * ANIMATION_FRAME_RATE: | |
py5.save_frame('/tmp/holiday/frame_####.png') | |
if py5.frame_count == (ANIMATION_START + ANIMATION_LENGTH) * ANIMATION_FRAME_RATE: | |
py5.exit_sketch() | |
py5.run_sketch() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment