Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@hx2A
Created January 4, 2021 15:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hx2A/1b717cf958932a97a06a860d83c2fd85 to your computer and use it in GitHub Desktop.
Save hx2A/1b717cf958932a97a06a860d83c2fd85 to your computer and use it in GitHub Desktop.
py5 source code for 2020 Holiday Animation
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