From fafaebb051907a848a9406f9da19669c81a83a3b Mon Sep 17 00:00:00 2001 From: Elvis Claros Castro Date: Sat, 26 Sep 2026 20:50:41 -0300 Subject: Translate code, comments and logs to English; English README; configurable paths and env vars Identifiers, docstrings, comments and console messages are now in English. Narration, subtitles and on-screen text stay in Spanish (they are the video content). The Blender <-> Godot physics protocol uses English keys and body prefixes chosen to keep the original creation order, so cached simulations and renders stay bit-identical. The old Spanish environment variable names are still accepted. --- blender/galton.py | 398 +++++++++++++++++++++++++++--------------------------- 1 file changed, 199 insertions(+), 199 deletions(-) (limited to 'blender/galton.py') diff --git a/blender/galton.py b/blender/galton.py index 522bc81..3717eb7 100644 --- a/blender/galton.py +++ b/blender/galton.py @@ -1,47 +1,47 @@ # -*- coding: utf-8 -*- -"""GALTON - 500 bolitas de acero contra 12 filas de clavos. +"""GALTON - 500 steel balls against 12 rows of pegs. -Reparto del trabajo: - * Blender disena el tablero (clavos, tolva, compuerta, casilleros, bolitas) - y lo exporta a glTF: el nombre de cada objeto le dice a Godot que es. - * Godot (Jolt) simula los rebotes, a 240 Hz, y devuelve la pose de cada - bolita a 30 fps (godot/sim.gd). - * Blender vuelve a leer esas poses y renderiza con los materiales de verdad. +Division of labour: + * Blender designs the board (pegs, hopper, gate, bins, balls) and exports + it to glTF: each object's name tells Godot what it is. + * Godot (Jolt) simulates the bounces at 240 Hz and returns the pose of each + ball at 30 fps (godot/sim.gd). + * Blender reads those poses back and renders with the real materials. - MODO=sim blender -b -P blender/galton.py # exporta y corre Godot - blender -b -P blender/galton.py # renderiza + MODE=sim blender -b -P blender/galton.py # exports and runs Godot + blender -b -P blender/galton.py # renders """ import math, os, random, sys sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import bpy from base import * -NOMBRE = "galton" +NAME_KEY = "galton" N_BOLAS = 500 -RB = 0.012 # radio de la bolita -DX = 0.12 # paso entre clavos +RB = 0.012 # ball radius +DX = 0.12 # peg pitch DY = DX * math.sqrt(3) / 2 -RP = 0.015 # radio del clavo -FILAS = 12 -NB = FILAS + 1 # casilleros -W2 = NB * DX / 2 # medio ancho interior -ALTO_CAS = 0.65 # alto de los separadores -Z_ULT = ALTO_CAS + 0.07 # ultima fila de clavos -Z_FILA = [Z_ULT + (FILAS - 1 - i) * DY for i in range(FILAS)] -Z_SAL = Z_FILA[0] + 0.10 # boca de la tolva -BOCA = float(os.environ.get("T_BOCA", 0.04)) # media boca -TOLVA_X, TOLVA_H, TOLVA_V = 0.33, 0.48, 0.62 -PROF = 0.08 # profundidad del canal (Y) -GRAV = float(os.environ.get("T_G", 12.0)) -PRE = 2.5 # segundos de fisica antes del frame 1: las bolitas se asientan -BEAT_UNA = 1 # cae una sola bolita -BEAT_SUELTA = 3 # se abre la compuerta -REB = float(os.environ.get("T_REB", 0.10)) -AMORT = float(os.environ.get("T_AM", 6.0)) - - -def x_clavos(i): - off = 0.5 if (FILAS - 1 - i) % 2 == 0 else 0.0 +RP = 0.015 # peg radius +ROW_COUNT = 12 +NB = ROW_COUNT + 1 # bins +W2 = NB * DX / 2 # half inner width +HOUSE_H = 0.65 # divider height +Z_ULT = HOUSE_H + 0.07 # last row of pegs +Z_ROW = [Z_ULT + (ROW_COUNT - 1 - i) * DY for i in range(ROW_COUNT)] +Z_EXIT = Z_ROW[0] + 0.10 # hopper mouth +MOUTH = float(env("T_MOUTH", None, 0.04)) # half mouth width +HOPPER_X, HOPPER_H, HOPPER_V = 0.33, 0.48, 0.62 +DEPTH = 0.08 # channel depth (Y) +GRAVITY = float(os.environ.get("T_G", 12.0)) +PRE_ROLL = 2.5 # seconds of physics before frame 1: the balls settle +BEAT_ONE = 1 # a single ball drops +BEAT_RELEASE = 3 # the gate opens +BOUNCE = float(env("T_BOUNCE", None, 0.10)) +DAMP = float(env("T_DAMP", None, 6.0)) + + +def x_pegs(i): + off = 0.5 if (ROW_COUNT - 1 - i) % 2 == 0 else 0.0 xs = [] j = -10 while True: @@ -54,124 +54,124 @@ def x_clavos(i): return xs -# --- diseno ------------------------------------------------------------------- -def construir(): - sc = escena(muestras=int(os.environ.get("MUESTRAS", 24))) - mundo_estudio(fuerza=0.6) +# --- design ------------------------------------------------------------------- +def build_scene(): + sc = scene_setup(samples=int(env("SAMPLES", "MUESTRAS", 24))) + studio_world(force=0.6) M = { - "laton": metal("laton", "#D9A441", rug=0.22), - "acero": metal("acero", "#E4E8EE", rug=0.10), - "chapa": metal("chapa", "#A9B2BF", rug=0.30), - "panel": material("panel", "#070B14", rug=0.75), + "laton": metal("laton", "#D9A441", rough=0.22), + "acero": metal("acero", "#E4E8EE", rough=0.10), + "chapa": metal("chapa", "#A9B2BF", rough=0.30), + "panel": material("panel", "#070B14", rough=0.75), "madera": madera("madera"), - "base": madera("base", claro="#7A5230", oscuro="#3A2211", escala=3.0), + "base": madera("base", light_c="#7A5230", dark="#3A2211", scale_to=3.0), } - fis = [] # lo que va a Godot + phys = [] # what goes to Godot - # clavos: cilindros a lo largo de Y, del panel del fondo al vidrio - for i, z in enumerate(Z_FILA): - for k, x in enumerate(x_clavos(i)): - c = cilindro(f"fijo_clavo_{i:02d}_{k:02d}", RP, PROF, M["laton"], lados=24) + # pegs: cylinders along Y, from the back panel to the glass + for i, z in enumerate(Z_ROW): + for k, x in enumerate(x_pegs(i)): + c = cylinder(f"static_clavo_{i:02d}_{k:02d}", RP, DEPTH, M["laton"], sides=24) c.rotation_euler = (math.pi / 2, 0, 0) c.location = (x, 0, z) - fis.append(c) + phys.append(c) - # separadores de los casilleros y paredes + # bin dividers and walls for k in range(NB + 1): x = (k - NB / 2) * DX ext = k in (0, NB) - grosor = 0.02 if ext else 0.006 - alto = (Z_SAL + 0.02) if ext else ALTO_CAS - xo = x + (-grosor / 2 if k == 0 else grosor / 2 if k == NB else 0) - fis.append(caja(f"caja_sep_{k:02d}", (grosor, PROF, alto), (xo, 0, alto / 2), + thickness_px = 0.02 if ext else 0.006 + alto = (Z_EXIT + 0.02) if ext else HOUSE_H + xo = x + (-thickness_px / 2 if k == 0 else thickness_px / 2 if k == NB else 0) + phys.append(box_obj(f"box_sep_{k:02d}", (thickness_px, DEPTH, alto), (xo, 0, alto / 2), M["chapa"])) - fis.append(caja("caja_piso", (2 * W2 + 0.04, PROF, 0.02), (0, 0, -0.01), M["chapa"])) + phys.append(box_obj("box_piso", (2 * W2 + 0.04, DEPTH, 0.02), (0, 0, -0.01), M["chapa"])) - # tolva: dos planos a 60 grados y un tramo vertical - dx_t, dz_t = TOLVA_X - BOCA, TOLVA_H + # hopper: two planes at 60 degrees and a vertical section + dx_t, dz_t = HOPPER_X - MOUTH, HOPPER_H largo = math.hypot(dx_t, dz_t) ang = math.atan2(dz_t, dx_t) for s in (-1, 1): - cx = s * (BOCA + dx_t / 2) - cz = Z_SAL + dz_t / 2 - # el espesor va hacia afuera de la tolva + cx = s * (MOUTH + dx_t / 2) + cz = Z_EXIT + dz_t / 2 + # the thickness goes outwards from the hopper nx, nz = s * math.sin(ang), -math.cos(ang) - w = caja(f"caja_tolva_{'i' if s < 0 else 'd'}", (largo, PROF, 0.016), + w = box_obj(f"box_tolva_{'i' if s < 0 else 'd'}", (largo, DEPTH, 0.016), (cx + nx * 0.008, 0, cz + nz * 0.008), M["chapa"]) w.rotation_euler = (0, -s * ang, 0) - fis.append(w) - v = caja(f"caja_tolva_v{'i' if s < 0 else 'd'}", (0.016, PROF, TOLVA_V), - (s * (TOLVA_X + 0.008), 0, Z_SAL + TOLVA_H + TOLVA_V / 2), M["chapa"]) - fis.append(v) - compuerta = caja("compuerta_0", (2 * BOCA + 0.04, PROF, 0.012), - (0, 0, Z_SAL - 0.008), M["chapa"]) - fis.append(compuerta) - - # bolitas: una grilla arriba de la tolva; caen y se acomodan antes del frame 1 - me_bola = esfera("bola_malla", RB, M["acero"], seg=20, anillos=12).data - bpy.data.objects.remove(bpy.data.objects["bola_malla"]) + phys.append(w) + v = box_obj(f"box_tolva_v{'i' if s < 0 else 'd'}", (0.016, DEPTH, HOPPER_V), + (s * (HOPPER_X + 0.008), 0, Z_EXIT + HOPPER_H + HOPPER_V / 2), M["chapa"]) + phys.append(v) + gate = box_obj("gate_0", (2 * MOUTH + 0.04, DEPTH, 0.012), + (0, 0, Z_EXIT - 0.008), M["chapa"]) + phys.append(gate) + + # balls: a grid above the hopper; they fall and settle before frame 1 + me_bola = sphere("ball_malla", RB, M["acero"], seg_m=20, rings=12).data + bpy.data.objects.remove(bpy.data.objects["ball_malla"]) bolas = [] rnd = random.Random(7) - paso = 2 * RB * 1.08 - por_fila = int((2 * (TOLVA_X - 0.02)) / paso) - sola = objeto("bola_000", me_bola, None) # la que cae sola, retenida bajo la compuerta - sola.location = (0.004, 0, Z_SAL - 0.032) - bolas.append(sola) + step = 2 * RB * 1.08 + per_row = int((2 * (HOPPER_X - 0.02)) / step) + alone = make_object("ball_000", me_bola, None) # the one that drops alone, held under the gate + alone.location = (0.004, 0, Z_EXIT - 0.032) + bolas.append(alone) for n in range(1, N_BOLAS): - fila, col = divmod(n - 1, por_fila) - x = (col - (por_fila - 1) / 2) * paso + (paso / 2 if fila % 2 else 0) * 0.5 + row, col = divmod(n - 1, per_row) + x = (col - (per_row - 1) / 2) * step + (step / 2 if row % 2 else 0) * 0.5 x += rnd.uniform(-0.002, 0.002) - z = Z_SAL + TOLVA_H + 0.03 + fila * paso * 0.95 - ob = objeto(f"bola_{n:03d}", me_bola, None) + z = Z_EXIT + HOPPER_H + 0.03 + row * step * 0.95 + ob = make_object(f"ball_{n:03d}", me_bola, None) ob.location = (x, 0, z) bolas.append(ob) - fis += bolas + phys += bolas - # --- lo que es solo visual --- - panel = caja("panel", (2 * W2 + 0.10, 0.02, Z_SAL + TOLVA_H + TOLVA_V + 0.10), - (0, PROF / 2 + 0.01, (Z_SAL + TOLVA_H + TOLVA_V) / 2), M["panel"]) + # --- visual only --- + panel = box_obj("panel", (2 * W2 + 0.10, 0.02, Z_EXIT + HOPPER_H + HOPPER_V + 0.10), + (0, DEPTH / 2 + 0.01, (Z_EXIT + HOPPER_H + HOPPER_V) / 2), M["panel"]) for s in (-1, 1): - caja(f"marco_{s}", (0.07, PROF + 0.06, Z_SAL + 0.12), - (s * (W2 + 0.055), 0.0, (Z_SAL + 0.12) / 2 - 0.02), M["madera"]) - caja("base", (2 * W2 + 0.40, 0.36, 0.10), (0, 0.05, -0.07), M["base"]) - caja("pie", (2 * W2 + 0.60, 0.50, 0.04), (0, 0.05, -0.14), M["base"]) - return dict(fis=fis, bolas=bolas, M=M, compuerta=compuerta) + box_obj(f"marco_{s}", (0.07, DEPTH + 0.06, Z_EXIT + 0.12), + (s * (W2 + 0.055), 0.0, (Z_EXIT + 0.12) / 2 - 0.02), M["madera"]) + box_obj("base", (2 * W2 + 0.40, 0.36, 0.10), (0, 0.05, -0.07), M["base"]) + box_obj("pie", (2 * W2 + 0.60, 0.50, 0.04), (0, 0.05, -0.14), M["base"]) + return dict(phys=phys, bolas=bolas, M=M, gate=gate) -def simular(T, obj): - f_suelta = T.rango(BEAT_SUELTA)[0] - f_una = T.rango(BEAT_UNA)[0] + 8 +def simulate(T, obj): + f_release = T.span(BEAT_RELEASE)[0] + f_one = T.span(BEAT_ONE)[0] + 8 cfg = { - "congelados": ["bola_000"], - "duracion": PRE + T.n_frames / FPS + 0.2, - "gravedad": GRAV, - "reglas": { - "bola_": {"friccion": 0.10, "rebote": REB, "densidad": 7800, "ccd": True, - "plano": True, "amortiguar": AMORT, "dormir": False}, - "fijo_": {"friccion": 0.10, "rebote": REB}, - "caja_": {"friccion": 0.25, "rebote": 0.20}, - "compuerta_": {"friccion": 0.25, "rebote": 0.0}, + "frozen": ["ball_000"], + "duration": PRE_ROLL + T.n_frames / FPS + 0.2, + "gravity": GRAVITY, + "rules": { + "ball_": {"friction": 0.10, "bounce": BOUNCE, "density": 7800, "ccd": True, + "planar": True, "damping": DAMP, "can_sleep": False}, + "static_": {"friction": 0.10, "bounce": BOUNCE}, + "box_": {"friction": 0.25, "bounce": 0.20}, + "gate_": {"friction": 0.25, "bounce": 0.0}, }, - "eventos": [{"t": PRE + (f_una - 1) / FPS, "accion": "soltar", "prefijo": "bola_000"}, - {"t": PRE + (f_suelta - 1) / FPS, "accion": "quitar", "prefijo": "compuerta_"}], + "events": [{"t": PRE_ROLL + (f_one - 1) / FPS, "action": "release", "prefix": "ball_000"}, + {"t": PRE_ROLL + (f_release - 1) / FPS, "action": "remove", "prefix": "gate_"}], } - correr_godot(exportar_fisica(NOMBRE, obj["fis"], cfg)) + run_godot(export_physics(NAME_KEY, obj["phys"], cfg)) -# --- trazos para explicar ----------------------------------------------------- -def camino(movs): - """Polilinea de un camino por la grilla: movs = lista de -1/+1.""" - pts = [(0.0, -0.05, Z_SAL - 0.02)] +# --- explanatory strokes --------------------------------------------------------- +def path_line(movs): + """Polyline of a path through the grid: movs = list of -1/+1.""" + pts = [(0.0, -0.05, Z_EXIT - 0.02)] x = 0.0 for i, m in enumerate(movs): - pts.append((x, -0.05, Z_FILA[i] + RP + RB)) + pts.append((x, -0.05, Z_ROW[i] + RP + RB)) x += m * DX / 2 pts.append((x, -0.05, 0.35)) return pts -def recortar(pts, u): - """Primeros u (0..1) del largo de la polilinea.""" +def trim(pts, u): + """First u (0..1) of the polyline length.""" L = [0.0] for a, b in zip(pts, pts[1:]): L.append(L[-1] + math.dist(a, b)) @@ -191,126 +191,126 @@ def recortar(pts, u): def main(): - T = Tiempo(NOMBRE) - obj = construir() - if os.environ.get("MODO") == "sim": - simular(T, obj) + T = Timeline(NAME_KEY) + obj = build_scene() + if env("MODE", "MODO") == "sim": + simulate(T, obj) return - idx, D = cargar_sim(NOMBRE) - pre = int(round(PRE * FPS)) + idx, D = load_sim(NAME_KEY) + pre_frames = int(round(PRE_ROLL * FPS)) bolas = obj["bolas"] ib = [idx[b.name] for b in bolas] import numpy as np xs_fin = D[-1, ib, 0] zs_fin = D[-1, ib, 2] - cas = np.clip(np.round(xs_fin / DX).astype(int) + FILAS // 2, 0, NB - 1) - cuenta = np.bincount(cas, minlength=NB) - print(f"[{NOMBRE}] casilleros: {cuenta.tolist()} (en el tablero: {int((zs_fin < ALTO_CAS).sum())})") - - # la bolita que se destaca: la que cae sola en el beat 1 - i_una = idx["bola_000"] - una = next(b for b in bolas if b.name == "bola_000") - f_sale = int(np.argmax(D[:, i_una, 2] < Z_SAL - 0.05)) - pre + 1 - f_llega = int(np.argmax(D[:, i_una, 2] < 0.6)) - pre + 1 - m_una = material("una", "rosa", rug=0.2, metal=0.3, emis=1.6) - estela = curva_poly("estela", [[(0, 0, 0)] * 24], grosor=0.006, - radios=[[0.0] * 24], mat=material("estela", "rosa", emis=2.5)) - - # caminos: el de la punta (beat 5) y varios al medio (beat 6) - punta = curva_poly("c_punta", [camino([-1] * FILAS)], grosor=0.010, - mat=material("cp", "rosa", emis=3.0)) + house = np.clip(np.round(xs_fin / DX).astype(int) + ROW_COUNT // 2, 0, NB - 1) + count = np.bincount(house, minlength=NB) + print(f"[{NAME_KEY}] bins: {count.tolist()} (on the board: {int((zs_fin < HOUSE_H).sum())})") + + # the highlighted ball: the one that drops alone in beat 1 + i_one = idx["ball_000"] + one_item = next(b for b in bolas if b.name == "ball_000") + f_sale = int(np.argmax(D[:, i_one, 2] < Z_EXIT - 0.05)) - pre_frames + 1 + f_arrive = int(np.argmax(D[:, i_one, 2] < 0.6)) - pre_frames + 1 + m_one = material("una", "rosa", rough=0.2, metal=0.3, emit=1.6) + estela = curve_poly("estela", [[(0, 0, 0)] * 24], thickness_px=0.006, + radios=[[0.0] * 24], mat=material("estela", "rosa", emit=2.5)) + + # paths: the edge one (beat 5) and several through the middle (beat 6) + tip_pt = curve_poly("c_punta", [path_line([-1] * ROW_COUNT)], thickness_px=0.010, + mat=material("cp", "rosa", emit=3.0)) rnd = random.Random(3) - medios = [] + mids = [] for k in range(10): m = [-1] * 6 + [1] * 6 rnd.shuffle(m) - medios.append(camino(m)) - c_medios = curva_poly("c_medios", medios, grosor=0.006, - mat=material("cm", "verde", emis=2.6)) + mids.append(path_line(m)) + c_mids = curve_poly("c_medios", mids, thickness_px=0.006, + mat=material("cm", "verde", emit=2.6)) - # campana: normal ajustada a las alturas finales de cada casillero - alturas = [] + # bell: normal curve fitted to the final heights of each bin + heights = [] for k in range(NB): - sel = zs_fin[cas == k] - alturas.append(float(sel.max() + RB) if len(sel) else 0.0) - mu = float(np.mean(xs_fin[zs_fin < ALTO_CAS])) - sd = float(np.std(xs_fin[zs_fin < ALTO_CAS])) - area = sum(alturas) * DX + sel = zs_fin[house == k] + heights.append(float(sel.max() + RB) if len(sel) else 0.0) + mu = float(np.mean(xs_fin[zs_fin < HOUSE_H])) + sd = float(np.std(xs_fin[zs_fin < HOUSE_H])) + area = sum(heights) * DX xs_c = [(-W2 + 2 * W2 * i / 120) for i in range(121)] - campana_pts = [(x, -0.05, 0.004 + area / (sd * math.sqrt(2 * math.pi)) + bell_pts = [(x, -0.05, 0.004 + area / (sd * math.sqrt(2 * math.pi)) * math.exp(-0.5 * ((x - mu) / sd) ** 2)) for x in xs_c] - campana = curva_poly("campana", [campana_pts], grosor=0.012, - mat=material("cc", "ambar", emis=3.2)) - print(f"[{NOMBRE}] mu={mu:.3f} sd={sd:.3f} (= {sd / (DX / 2):.2f} medios pasos; " - f"binomial ideal: {math.sqrt(FILAS) / 2 * 2:.2f})") - - # marco del casillero del medio (beat 10) - m_marco = material("marco_medio", "verde", emis=2.8) - h_med = alturas[NB // 2] + 0.03 - marco = curva_poly("marco_medio", [[(-DX / 2, -0.05, 0.0), (-DX / 2, -0.05, h_med), + bell = curve_poly("campana", [bell_pts], thickness_px=0.012, + mat=material("cc", "ambar", emit=3.2)) + print(f"[{NAME_KEY}] mu={mu:.3f} sd={sd:.3f} (= {sd / (DX / 2):.2f} medios pasos; " + f"binomial ideal: {math.sqrt(ROW_COUNT) / 2 * 2:.2f})") + + # frame around the middle bin (beat 10) + m_frame = material("marco_medio", "verde", emit=2.8) + h_med = heights[NB // 2] + 0.03 + frame_box = curve_poly("marco_medio", [[(-DX / 2, -0.05, 0.0), (-DX / 2, -0.05, h_med), (DX / 2, -0.05, h_med), (DX / 2, -0.05, 0.0)]], - grosor=0.008, mat=m_marco) + thickness_px=0.008, mat=m_frame) - # camara: de la tolva al tablero entero - lente = 50.0 - cam = camara((0, -5, 1), (0, 0, 1), lente=lente) + # camera: from the hopper to the whole board + lens = 50.0 + cam = camera_obj((0, -5, 1), (0, 0, 1), lens=lens) cam.data.sensor_fit = 'VERTICAL' cam.data.sensor_height = 36.0 - A = (2.10, 2.0) # (z centro, alto visible) + A = (2.10, 2.0) # (z centre, visible height) B = (0.60, 3.10) - luz("key", 'AREA', (-1.8, -3.0, 3.2), 520, "blanco", tam=3.0, mira=(0, 0, 1.2)) - luz("fill", 'AREA', (2.4, -2.6, 0.6), 110, "blanco", tam=3.0, mira=(0, 0, 0.8)) - luz("top", 'AREA', (0.0, -0.8, 3.6), 220, "blanco", tam=2.0, mira=(0, 0, 1.5)) + light_obj("key", 'AREA', (-1.8, -3.0, 3.2), 520, "blanco", size_u=3.0, sight=(0, 0, 1.2)) + light_obj("fill", 'AREA', (2.4, -2.6, 0.6), 110, "blanco", size_u=3.0, sight=(0, 0, 0.8)) + light_obj("top", 'AREA', (0.0, -0.8, 3.6), 220, "blanco", size_u=2.0, sight=(0, 0, 1.5)) - def actualizar(f): - i = min(f - 1 + pre, len(D) - 1) + def refresh(f): + i = min(f - 1 + pre_frames, len(D) - 1) for b, j in zip(bolas, ib): - poner_pose(b, D[i, j]) - - # camara - m = suave((f - f_sale) / max(1, f_llega - f_sale + 10)) - zc = mezcla(A[0], B[0], m) - h = mezcla(A[1], B[1], m) - deriva = 0.03 * math.sin(f / 90.0) - dist = h * lente / 36.0 - apuntar(cam, (deriva, -dist, zc + 0.08), (deriva * 0.5, 0, zc)) - - # la bolita destacada - vis = 1.0 if (T.rango(1)[0] <= f <= T.rango(4)[0]) else 0.0 - if vis: - if una.material_slots and una.material_slots[0].link != 'OBJECT': - una.material_slots[0].link = 'OBJECT' - if una.material_slots: - una.material_slots[0].material = m_una - elif una.material_slots: - una.material_slots[0].link = 'DATA' + set_pose(b, D[i, j]) + + # camera + m = suave((f - f_sale) / max(1, f_arrive - f_sale + 10)) + zc = mix_m(A[0], B[0], m) + h = mix_m(A[1], B[1], m) + drift = 0.03 * math.sin(f / 90.0) + dist = h * lens / 36.0 + aim_at(cam, (drift, -dist, zc + 0.08), (drift * 0.5, 0, zc)) + + # the highlighted ball + visible = 1.0 if (T.span(1)[0] <= f <= T.span(4)[0]) else 0.0 + if visible: + if one_item.material_slots and one_item.material_slots[0].link != 'OBJECT': + one_item.material_slots[0].link = 'OBJECT' + if one_item.material_slots: + one_item.material_slots[0].material = m_one + elif one_item.material_slots: + one_item.material_slots[0].link = 'DATA' pts, rad = [], [] for k in range(24): - q = D[max(0, i - (23 - k)), i_una] + q = D[max(0, i - (23 - k)), i_one] pts.append((q[0], -0.03, q[2])) - rad.append(vis * (k / 23.0) ** 1.5) - rehacer_curva(estela, [pts], [rad]) + rad.append(visible * (k / 23.0) ** 1.5) + rebuild_curve(estela, [pts], [rad]) - # caminos + # paths up = suave(T.p(f, 5) / 0.7) * (1.0 - suave((T.p(f, 7) - 0.0) / 0.2)) - rehacer_curva(punta, [recortar(camino([-1] * FILAS), up)]) - punta.scale = (1, 1, 1) if 0 < up else (0, 0, 0) + rebuild_curve(tip_pt, [trim(path_line([-1] * ROW_COUNT), up)]) + tip_pt.scale = (1, 1, 1) if 0 < up else (0, 0, 0) cm = [] - for k, c in enumerate(medios): + for k, c in enumerate(mids): u = suave((T.p(f, 6) - k * 0.06) / 0.35) - cm.append(recortar(c, u)) - rehacer_curva(c_medios, cm) - fuera = 1.0 - suave(T.p(f, 7) / 0.2) - c_medios.scale = (1, 1, 1) if (T.p(f, 6) > 0 and fuera > 0.01) else (0, 0, 0) + cm.append(trim(c, u)) + rebuild_curve(c_mids, cm) + outside = 1.0 - suave(T.p(f, 7) / 0.2) + c_mids.scale = (1, 1, 1) if (T.p(f, 6) > 0 and outside > 0.01) else (0, 0, 0) - # campana + # bell uc = suave(T.p(f, 7) / 0.6) - rehacer_curva(campana, [recortar(campana_pts, uc)]) - campana.scale = (1, 1, 1) if uc > 0 else (0, 0, 0) + rebuild_curve(bell, [trim(bell_pts, uc)]) + bell.scale = (1, 1, 1) if uc > 0 else (0, 0, 0) - marco.scale = (1, 1, 1) if T.p(f, 10) > 0.02 else (0, 0, 0) + frame_box.scale = (1, 1, 1) if T.p(f, 10) > 0.02 else (0, 0, 0) - render_secuencia(NOMBRE, T, actualizar) + render_sequence(NAME_KEY, T, refresh) main() -- cgit v1.2.3