aboutsummaryrefslogtreecommitdiffstats
path: root/blender/galton.py
diff options
context:
space:
mode:
authorElvis Claros Castro <elvis@claros.ar>2026-09-26 20:50:41 -0300
committerElvis Claros Castro <elvis@claros.ar>2026-09-26 20:50:41 -0300
commitfafaebb051907a848a9406f9da19669c81a83a3b (patch)
treec30ea26e6b549e5523af2bae5c39569e9a946b12 /blender/galton.py
parent59355909f2de9236af8168a26c70bcf6caa3b285 (diff)
download100cia-videos-fafaebb051907a848a9406f9da19669c81a83a3b.tar.gz
100cia-videos-fafaebb051907a848a9406f9da19669c81a83a3b.zip
Translate code, comments and logs to English; English README; configurable paths and env varsHEADmain
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.
Diffstat (limited to 'blender/galton.py')
-rw-r--r--blender/galton.py398
1 files changed, 199 insertions, 199 deletions
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()