Skip to content

Instantly share code, notes, and snippets.

@ylegall
Created February 11, 2021 19:38
Show Gist options
  • Save ylegall/155db9496e43369ce4f752303b16a413 to your computer and use it in GitHub Desktop.
Save ylegall/155db9496e43369ce4f752303b16a413 to your computer and use it in GitHub Desktop.
import bpy
import bmesh
import random
from mathutils import Vector, noise, Matrix
from math import sin, cos, tau, pi, modf
from utils.interpolation import *
from utils.math import *
from utils.color import *
from easing_functions import QuadEaseOut
# https://sinestesia.co/blog/tutorials/python-cube-matrices/
frame_start = 1
total_frames = 16 * 30
bpy.context.scene.render.fps = 30
bpy.context.scene.frame_start = frame_start
bpy.context.scene.frame_end = total_frames
random.seed(7)
size = 2
max_levels = 2
padding = 0.961
colors1 = [0xF6E8F3, 0xF6E8F3, 0xeebe96, 0xef709d, 0x4b4e6d, 0x66DAB0]
colors2 = [0xe0fbfc, 0xd9cc98, 0x293241, 0x3d5a80, 0xe0fbfc, 0xee6c4d]
material_assignments = [random.randrange(len(colors1)) for i in range(8**max_levels)]
# splits = [random.uniform(0.1, 0.9) for _ in range(8**max_levels)]
splits = {}
for i in range(8**max_levels):
split_values = [random.uniform(0.15, 0.85) for _ in range(6)]
split_values += split_values[:1]
splits[i] = split_values
def setup():
col = bpy.data.collections.get('generated')
if not col:
col = bpy.data.collections.new('generated')
bpy.context.scene.collection.children.link(col)
cubes_obj = bpy.data.objects.new('cubes', bpy.data.meshes.new('cube-mesh'))
col.objects.link(cubes_obj)
bpy.ops.mesh.primitive_cube_add(size=2.0)
base_cube = bpy.context.object
base_cube.name = 'base_cube'
base_cube.hide_render = True
base_cube.hide_viewport = True
col.objects.link(base_cube)
bpy.context.scene.collection.objects.unlink(base_cube)
for i, color in enumerate(colors1):
material = bpy.data.materials.new(f'mat-{i}')
material.diffuse_color = hex_to_rgb(color)
material.roughness = 0.35
material.specular_intensity = 0.7
def get_cube_center(start: Vector, end: Vector) -> Matrix:
return Matrix.Translation(start.lerp(end, 0.5))
def get_cube_scale(start: Vector, end: Vector) -> Matrix:
scale_values = ((end - start) * 0.5) * padding
return scale_matrix_from_vec3(scale_values)
def get_noise_value(split_id: int, t: float) -> float:
tf, ti = modf(t * 6)
t1 = (ti + QuadEaseOut().ease(smoothstep(0.15, 0.65, tf))) / 6
# noise_pos1 = Vector((axis * 3, axis * 5, axis * 7))
# noise_pos2 = polar(tau * t1, 1.3)
# return 0.5 + 0.3 * noise.noise(noise_pos1 + noise_pos2, noise_basis='BLENDER')
return mix_values(splits[split_id], t1)
# return 0.5
def update_mesh_recursive(
t: float,
start_coord: Vector,
end_coord: Vector,
axis: int,
level: int,
seed: [int],
transforms
):
if level >= max_levels:
translation = get_cube_center(start_coord, end_coord)
scale_matrix = get_cube_scale(start_coord, end_coord)
transforms.append(translation @ scale_matrix)
else:
start = start_coord[axis]
end = end_coord[axis]
# split_pct = get_noise_value(axis + level * 3, t)
split_pct = get_noise_value(seed[0], t)
split = mix(start, end, split_pct)
seed[0] += 1
new_start = start_coord.copy()
new_start[axis] = split
new_end = end_coord.copy()
new_end[axis] = split
new_level = level + 1 if axis == 2 else level
new_axis = (axis + 1) % 3
update_mesh_recursive(t, start_coord.copy(), new_end, new_axis, new_level, seed, transforms)
update_mesh_recursive(t, new_start, end_coord.copy(), new_axis, new_level, seed, transforms)
# update_mesh_recursive(t, start_coord.copy(), new_end, new_axis, new_level, seed * 2 + 1, transforms)
# update_mesh_recursive(t, new_start, end_coord.copy(), new_axis, new_level, seed * 2 + 2, transforms)
def update_mesh(t: float) -> bpy.types.Mesh:
transforms = []
start_coord = Vector((-size, -size, -size))
end_coord = Vector((size, size, size))
update_mesh_recursive(t, start_coord, end_coord, 0, 0, [0], transforms)
bm = bmesh.new()
base_cube = bpy.data.objects.get('base_cube')
new_mesh = bpy.data.meshes.new('cube-mesh')
for i in range(len(colors1)):
new_mesh.materials.append(bpy.data.materials.get(f'mat-{i}'))
for i, tx in enumerate(transforms):
cube_mesh = base_cube.data.copy()
cube_mesh.transform(tx)
for face in cube_mesh.polygons:
face.material_index = material_assignments[i]
face.use_smooth = True
bm.from_mesh(cube_mesh)
bpy.data.meshes.remove(cube_mesh)
bmesh.ops.bevel(bm,
geom=bm.edges,
offset=0.037,
offset_type='OFFSET',
segments=3,
profile=0.5,
affect='EDGES',
material=-1
)
bm.to_mesh(new_mesh)
bm.free()
return new_mesh
def get_cubes_rotation(t: float):
p2 = pi / 2
t1 = t * 6
rx = p2 * smoothstep(0.8, 1.0, t1)
ry = p2 * smoothstep(1.8, 2.0, t1)
rz = p2 * smoothstep(2.8, 3.0, t1)
ry = ry - p2 * smoothstep(5.8, 6.0, t1)
rx = rx - p2 * smoothstep(3.8, 4.0, t1)
rz = rz - p2 * smoothstep(4.8, 5.0, t1)
return rx, ry, rz
def update_material_colors(t: float):
t1 = (t * 4) % 1.0
pct = smoothstep(0.1, 0.3, t1) - smoothstep(0.6, 0.9, t1)
for i in range(len(colors1)):
mat = bpy.data.materials.get(f'mat-{i}')
mat.diffuse_color = mix_rgb(hex_to_rgb(colors1[i]), hex_to_rgb(colors2[i]), pct)
def frame_update(scene):
frame = scene.frame_current
t = (((frame - 1) % total_frames) + 1) / float(total_frames)
update_material_colors(t)
new_mesh = update_mesh(t)
cubes_obj = bpy.data.objects.get('cubes')
old_mesh = cubes_obj.data
cubes_obj.data = new_mesh
if old_mesh:
bpy.data.meshes.remove(old_mesh, do_unlink=True)
cubes_obj.rotation_euler = get_cubes_rotation(t)
setup()
bpy.app.handlers.frame_change_pre.clear()
bpy.app.handlers.frame_change_pre.append(frame_update)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment