Last active
April 8, 2021 17:58
-
-
Save ylegall/2cdb8c49841a5b1bfee3bead153488c4 to your computer and use it in GitHub Desktop.
blocks transformed by circle inversion
This file contains hidden or 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 bpy | |
import bmesh | |
import random | |
from easing_functions import CubicEaseInOut, QuadEaseInOut | |
from mathutils import Vector, noise, Matrix | |
from math import sin, cos, tau, pi, sqrt, radians | |
from utils.interpolation import * | |
from utils.math import * | |
from utils.color import * | |
frame_start = 1 | |
total_frames = 240 | |
bpy.context.scene.render.fps = 30 | |
bpy.context.scene.frame_start = frame_start | |
bpy.context.scene.frame_end = total_frames | |
random.seed(2) | |
size = 7.0 | |
circle_radius = 1.0 | |
post_scale = 2.0 | |
max_subdivision_levels = 10 | |
# colors = [0xbebbbb, 0x444054, 0x2f243a, 0xfac9b8, 0xdb8a74] | |
# colors = [0x0c0114, 0xEAECDF, 0xE9693F, 0x2fbae0, 0x3c6997] | |
colors = [0x292f36, 0xEAECDF, 0xE46338, 0x2fbae0, 0x3c6997] | |
material_assignments = [random.randint(0, len(colors)-1) for _ in range(31)] | |
block_idx = 0 | |
def make_materials(): | |
main_obj = bpy.data.objects['main'] | |
materials = main_obj.data.materials | |
materials.clear() | |
for i in range(len(colors)): | |
mat = bpy.data.materials.get(f'mat-{i}') or bpy.data.materials.new(f'mat-{i}') | |
mat.use_nodes = True | |
node = mat.node_tree.nodes['Principled BSDF'] | |
color = hex_to_rgb(colors[i]) | |
node.inputs[0].default_value = color # color | |
node.inputs[17].default_value = color # emission color | |
node.inputs[5].default_value = 0.40 # specular | |
node.inputs[4].default_value = 1.0 if i == 4 else 0.0 # metallic | |
node.inputs[7].default_value = 0.1 if i == 4 else 0.27 # roughness | |
node.inputs[18].default_value = 0.0 # emission | |
materials.append(mat) | |
def setup(): | |
col = bpy.data.collections.get('generated') | |
if not col: | |
col = bpy.data.collections.new('generated') | |
bpy.context.scene.collection.children.link(col) | |
obj = bpy.data.objects.get('main') | |
if not obj: | |
obj = bpy.data.objects.new('main', bpy.data.meshes.new('main')) | |
col.objects.link(obj) | |
original = bpy.data.objects['original'] | |
original.hide_viewport = True | |
original.hide_render = True | |
make_materials() | |
bevel = obj.modifiers.get('bevel') or obj.modifiers.new('bevel', type='BEVEL') | |
bevel.segments = 2 | |
bevel.width = 3.0 | |
bevel.offset_type = 'PERCENT' | |
bevel.angle_limit = radians(40.0) | |
obj.data.use_auto_smooth = True | |
obj.data.auto_smooth_angle = radians(20) | |
def reflect(point: Vector, radius: float) -> Vector: | |
dist = max(point.length, 0.0001) | |
new_dist = radius / dist | |
new_dist = new_dist if new_dist > 1 else linearstep(0.09, 1.0, new_dist) | |
# new_dist = QuadEaseInOut().ease(new_dist) | |
return point.normalized() * new_dist * radius | |
def noise_value(t: float, seed: int, radius: float = 0.23) -> float: | |
offset = polar(tau * t, radius) + Vector((seed * pi, 0.0, 0.0)) | |
return noise.noise(offset, noise_basis='BLENDER') | |
def noise_value_offset(t: float, offset: Vector, radius: float = 0.23) -> float: | |
offset = polar(tau * t, radius) + offset | |
return noise.noise(offset, noise_basis='BLENDER') | |
class Bounds: | |
def __init__(self, x0, x1, y0, y1): | |
self.x0 = x0 | |
self.x1 = x1 | |
self.y0 = y0 | |
self.y1 = y1 | |
def center(self) -> Vector: | |
center_x = (self.x0 + self.x1) / 2.0 | |
center_y = (self.y0 + self.y1) / 2.0 | |
return Vector((center_x, center_y, 0.0)) | |
def make_block( | |
t: float, | |
bounds: Bounds, | |
bm: bmesh.types.BMesh, | |
): | |
center = bounds.center() | |
if center.length <= 0.6: | |
return | |
mesh_copy = bpy.data.meshes['original'].copy() | |
translation = Matrix.Translation(bounds.center()) | |
scale_x = 0.93 * (bounds.x1 - bounds.x0) | |
scale_y = 0.93 * (bounds.y1 - bounds.y0) | |
scale = scale_matrix(scale_x, scale_y, 1.0) | |
mesh_copy.transform(translation @ scale) | |
mesh_copy.polygons[0].material_index = material_assignments[block_idx % len(material_assignments)] | |
bm.from_mesh(mesh_copy) | |
bpy.data.meshes.remove(mesh_copy, do_unlink=True) | |
def subdivide( | |
t: float, | |
bounds: Bounds, | |
bm: bmesh.types.BMesh, | |
level: int = 0, | |
split_seed: int = 0, | |
): | |
global block_idx | |
if level >= max_subdivision_levels: | |
make_block(t, bounds, bm) | |
block_idx += 1 | |
return | |
pct = noise_value(t, split_seed, radius=0.17) | |
pct = remap(pct, -1.0, 1.0, 0.15, 0.85) | |
is_x_split = level % 2 == 0 | |
split = mix(bounds.x0, bounds.x1, pct) if is_x_split else mix(bounds.y0, bounds.y1, pct) | |
new_low_bounds = Bounds(bounds.x0, split, bounds.y0, bounds.y1) if is_x_split else \ | |
Bounds(bounds.x0, bounds.x1, bounds.y0, split) | |
new_high_bounds = Bounds(split, bounds.x1, bounds.y0, bounds.y1) if is_x_split else \ | |
Bounds(bounds.x0, bounds.x1, split, bounds.y1) | |
subdivide(t, new_low_bounds, bm, level + 1, 2 * split_seed + 1) | |
subdivide(t, new_high_bounds, bm, level + 1, 2 * split_seed + 2) | |
def frame_update(scene): | |
global block_idx | |
frame = scene.frame_current | |
t = frame / float(total_frames) | |
block_idx = 0 | |
bm = bmesh.new() | |
subdivide(t, Bounds(-size, size, -size, size), bm) | |
# invert points | |
for vert in bm.verts: | |
vert.co = reflect(vert.co, circle_radius) | |
# dist = vert.co.length | |
# dist = remap(dist, 0.1, circle_radius, 0.0, 2.5 * circle_radius) | |
# vert.co = vert.co.normalized() * dist | |
vert.co *= post_scale | |
bmesh.ops.extrude_face_region(bm, geom=bm.faces) | |
for i, face in enumerate(bm.faces): | |
face.smooth = True | |
face.normal_update() | |
if face.normal.z > 0.9: | |
center = face.calc_center_median() | |
dist_factor = center.length | |
dist_factor = smoothstep(0.0, circle_radius + 1, dist_factor) | |
random_height = 0.6 + 0.3 * noise_value_offset(t, center, radius=0.47) | |
height = max(0.01, random_height * dist_factor) | |
for vert in face.verts: | |
vert.co.z = height | |
bm.to_mesh(bpy.data.meshes['main']) | |
bm.free() | |
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