From 59355909f2de9236af8168a26c70bcf6caa3b285 Mon Sep 17 00:00:00 2001 From: Elvis Claros Castro Date: Sat, 26 Sep 2026 20:21:47 -0300 Subject: Import video pipeline as it was --- blender/bloques.py | 279 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 blender/bloques.py (limited to 'blender/bloques.py') diff --git a/blender/bloques.py b/blender/bloques.py new file mode 100644 index 0000000..19e9991 --- /dev/null +++ b/blender/bloques.py @@ -0,0 +1,279 @@ +# -*- coding: utf-8 -*- +"""BLOQUES - la pila armonica que sobresale de la mesa sin limite. + +Blender disena (mesa, piso, bloques de madera) y exporta a glTF; Godot (Jolt) +decide que se sostiene y que se cae. Hay tres experimentos, cada uno en su +propio carril de la mesa (Y distinta) para que no se toquen; al renderizar se +trae al frente el que toca y se ocultan los otros. + + A: la escalera "obvia", cada bloque corrido medio largo -> se cae + B: 5 bloques armonicos (1/2, 1/4, 1/6, 1/8, 1/10) -> se sostiene + C: 16 bloques armonicos; en el beat 8 se le apoya uno mas -> se cae todo + +Los corrimientos armonicos se escalan por C_SEG = 0,95: asi el centro de masa +de cada subpila queda (1-C)/2 = 2,5 % de largo adentro del borde que la +sostiene, en todos los pisos. Sin ese margen, el solver decide al azar. +""" +import math, os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import bpy +from base import * + +NOMBRE = "bloques" +L, H, P = 1.0, 0.16, 0.50 # largo, alto, profundidad del bloque +C_SEG = 0.95 +C_TORRE = float(os.environ.get("T_C", C_SEG)) # la torre alta necesita mas margen con el solver +Z_PISO = -3.5 # mesa de 70 cm con bloques de 20 cm +GRAV = 49.0 # 9,81 / 0,2: bloques de 20 cm de verdad +CARRIL = {"a": 0.0, "b": 3.0, "c": 6.0, "x": 6.0} +N_B, N_C = 5, 16 +DX_EXTRA = float(os.environ.get("T_DX", -0.45)) # donde se apoya el bloque de mas + + +def pila(n, c=C_SEG): + """Centros x de una pila armonica de n bloques (indice 0 = el de arriba).""" + xs = [0.0] * n + x = -L / 2 # la mesa hace de bloque n+1 + for k in range(n, 0, -1): + x += c * L / (2 * k) + xs[k - 1] = x + return xs + + +def z_de(n, k): + """Altura del centro del bloque k (0 = arriba) en una pila de n.""" + return (n - 1 - k) * H + H / 2 + + +def construir(): + escena(muestras=int(os.environ.get("MUESTRAS", 24))) + mundo_estudio(fuerza=0.55) + M = { + "bloque": madera("bloque", claro="#E2B880", oscuro="#B07A45", escala=5.0, rug=0.5), + "arriba": material("arriba", "ambar", rug=0.35), + "mesa": madera("mesa", claro="#6B4426", oscuro="#2E1A0C", escala=2.5, rug=0.35, + veta=(0.4, 1, 1)), + "piso": madera("piso", claro="#3A3F4B", oscuro="#1E222B", escala=1.5, rug=0.6, + veta=(0.3, 1, 1)), + "invisible": material("inv", "gris"), + } + fis = [] + mesa = caja("caja_mesa", (6.0, 9.0, 0.3), (-3.0, 3.0, -0.15), M["invisible"]) + piso = caja("caja_piso", (30.0, 30.0, 0.2), (0.0, 3.0, Z_PISO - 0.1), M["invisible"]) + mesa.hide_render = piso.hide_render = True + fis += [mesa, piso] + # lo que se ve de la mesa y el piso: solo el carril del frente + caja("mesa_v", (6.0, 2.4, 0.3), (-3.0, 0.4, -0.15), M["mesa"]) + m_pata = metal("pata", "#2A2F3A", rug=0.4) + caja("pata", (0.14, 0.14, -Z_PISO - 0.3), (-0.35, -0.55, (Z_PISO - 0.3) / 2), m_pata) + caja("pata2", (0.14, 0.14, -Z_PISO - 0.3), (-0.35, 1.3, (Z_PISO - 0.3) / 2), m_pata) + caja("piso_v", (14.0, 5.0, 0.2), (1.0, 1.0, Z_PISO - 0.1), M["piso"]) + + bl = {} + # A: escalera "obvia" + for k in range(5): + x = -0.25 + 0.5 * (4 - k) + bl[f"a{k}"] = caja(f"bloque_a{k}", (L, P, H), (x, CARRIL["a"], z_de(5, k)), M["bloque"]) + # B: armonica de 5 + for k, x in enumerate(pila(N_B)): + bl[f"b{k}"] = caja(f"bloque_b{k}", (L, P, H), (x, CARRIL["b"], z_de(N_B, k)), + M["arriba"] if k == 0 else M["bloque"]) + # C: armonica de 16 y el bloque de mas, esperando arriba + xc = pila(N_C, C_TORRE) + for k, x in enumerate(xc): + bl[f"c{k:02d}"] = caja(f"bloque_c{k:02d}", (L, P, H), (x, CARRIL["c"], z_de(N_C, k)), + M["arriba"] if k == 0 else M["bloque"]) + bl["x"] = caja("bloque_x", (L, P, H), (xc[0] + DX_EXTRA, CARRIL["x"], z_de(N_C, 0) + H + 0.005), + M["bloque"]) + fis += list(bl.values()) + return dict(fis=fis, bl=bl, M=M) + + +def eventos(T): + t = lambda f: (f - 1) / FPS + fa = T.rango(1)[0] + int(0.55 * (T.rango(1)[1] - T.rango(1)[0])) + fx = T.rango(8)[0] + int(0.70 * (T.rango(8)[1] - T.rango(8)[0])) + return {"a": fa, "b": T.rango(4)[0], "c": T.rango(7)[0], "x": fx}, [ + {"t": t(fa), "accion": "soltar", "prefijo": "bloque_a"}, + {"t": t(T.rango(4)[0]), "accion": "soltar", "prefijo": "bloque_b"}, + {"t": t(T.rango(7)[0]), "accion": "soltar", "prefijo": "bloque_c"}, + {"t": t(fx), "accion": "soltar", "prefijo": "bloque_x"}, + ] + + +def simular(T, obj): + _, ev = eventos(T) + cfg = { + "duracion": T.n_frames / FPS + 0.2, + "gravedad": GRAV, + "escala": float(os.environ.get("T_ESC", 5.0)), + "hz": int(os.environ.get("T_HZ", 1920)), + "congelados": ["bloque_"], + "reglas": { + "bloque_": {"friccion": 0.55, "rebote": 0.08, "densidad": 600}, + "caja_": {"friccion": 0.6, "rebote": 0.05}, + }, + "eventos": ev, + } + correr_godot(exportar_fisica(NOMBRE, obj["fis"], cfg)) + + +def main(): + T = Tiempo(NOMBRE) + obj = construir() + if os.environ.get("MODO") == "sim": + simular(T, obj) + return + idx, D = cargar_sim(NOMBRE) + bl, M = obj["bl"], obj["M"] + suelta, _ = eventos(T) + xc = pila(N_C, C_TORRE) + + # reporte: que quedo en pie + for g, n in (("a", 5), ("b", N_B), ("c", N_C)): + nom = [k for k in bl if k.startswith(g) and k != "x"] + zmin = min(D[-1, idx[bl[k].name], 2] for k in nom) + zmin_antes = min(D[min(len(D) - 1, suelta["x"] - 5), idx[bl[k].name], 2] for k in nom) + print(f"[{NOMBRE}] pila {g}: z minima al final {zmin:+.2f} " + f"(antes del bloque extra {zmin_antes:+.2f})") + print(f"[{NOMBRE}] B: el de arriba arranca en x={pila(N_B)[0] - L / 2:+.3f} " + f"(sobresale {pila(N_B)[0] + L / 2:.3f}); C sobresale {xc[0] + L / 2:.3f}") + + # --- dibujos ------------------------------------------------------------- + m_borde = material("borde", "celeste", emis=2.5) + borde = curva_poly("borde", [[(0, -0.30, z0), (0, -0.30, z0 + 0.09)] + for z0 in [0.02 + 0.16 * i for i in range(18)]], + grosor=0.012, mat=m_borde) + etiquetas = [] + xb = pila(N_B) + for k in range(N_B): + num = texto(f"1/{2 * (k + 1)}", tam=0.15, color="ambar") + # el corrimiento de este bloque respecto del de abajo, a su derecha + num.location = (xb[k] + L / 2 + 0.16, -0.30, z_de(N_B, k)) + etiquetas.append(num) + aire = curva_poly("aire", [[(0, -0.3, 0.9), (xb[0] - L / 2, -0.3, 0.9)]], + grosor=0.02, mat=material("aire", "rosa", emis=3.0)) + aire_t = texto("en el aire", tam=0.16, color="rosa") + aire_t.location = (0.45, -0.3, 1.08) + # centros de masa de cada subpila, sobre el borde que la sostiene + cms = [] + for k in range(1, N_B + 1): + x_cm = sum(xb[:k]) / k + e = esfera(f"cm{k}", 0.045, material(f"mcm{k}", "ambar", emis=3.0)) + e.location = (x_cm, -0.30, z_de(N_B, k - 1) - H / 2) + cms.append(e) + cuenta = texto("16 bloques", tam=0.22, color="blanco") + sobre = texto("sobresale 1,6 bloques", tam=0.18, color="ambar") + regla = curva_poly("regla", [[(0, -0.3, 2.78), (xc[0] + L / 2, -0.3, 2.78)]], + grosor=0.018, mat=material("regla", "ambar", emis=3.0)) + + lente = 50.0 + cam = camara((0, -8, 1), (0, 0, 0), lente=lente) + cam.data.sensor_fit = 'VERTICAL' + cam.data.sensor_height = 36.0 + luz("key", 'AREA', (-3.0, -4.0, 5.0), 1400, "blanco", tam=4.0, mira=(0.5, 0, 0.8)) + luz("fill", 'AREA', (4.0, -3.0, 0.5), 350, "blanco", tam=4.0, mira=(0.5, 0, 0.5)) + luz("rim", 'AREA', (1.0, 4.0, 4.0), 600, "blanco", tam=3.0, mira=(0.5, 0, 1.0)) + + # (frame, x centro, z del contenido, alto visible) + r = T.rango + tomas = [ + (1, 0.55, 0.40, 5.6), + (suelta["a"] + 5, 0.55, 0.40, 5.6), + (r(1)[1] + 10, 0.9, -1.7, 7.4), + (r(2)[0] + 18, 0.45, 0.45, 4.8), + (r(5)[1], 0.45, 0.45, 4.8), + (r(6)[0] + 10, 0.50, 0.9, 5.6), + (r(6)[1], 0.55, 1.35, 6.4), + (r(8)[1] - 4, 0.55, 1.35, 6.4), + (r(9)[0] + 30, 1.2, -1.3, 8.4), + (T.n_frames, 1.2, -1.4, 8.6), + ] + + def camara_en(f): + for (f0, *a), (f1, *b) in zip(tomas, tomas[1:]): + if f0 <= f <= f1: + u = suave((f - f0) / max(1, f1 - f0)) + return [p + (q - p) * u for p, q in zip(a, b)] + return tomas[-1][1:] + + def n_c(f): + """Cuantos bloques tiene la pila C mientras se arma (beat 6).""" + return 5 + (N_C - 5) * suave(T.p(f, 6) / 0.8) + + def actualizar(f): + i = min(f - 1, len(D) - 1) + grupo = "a" if f < r(2)[0] + 10 else "b" if f < r(6)[0] else "c" + for k, ob in bl.items(): + g = "x" if k == "x" else k[0] + vis = (g == grupo) or (g == "x" and grupo == "c") + ob.hide_render = not vis + if not vis: + continue + fila = list(D[i, idx[ob.name]]) + fila[1] -= CARRIL[g] + poner_pose(ob, fila) + if g == "a" and f < suelta["a"]: + # de la pila prolija a la escalera, antes de soltarla + kk = int(k[1:]) + u = suave(T.p(f, 1) / 0.45) + ob.location.x = mezcla(-0.45, fila[0], u) + elif g == "b" and f < suelta["b"]: + # se arma desde arriba: cada bloque entra desde la izquierda + kk = int(k[1:]) + u = suave((T.p(f, 3) - kk * 0.17) / 0.16) if f >= r(3)[0] else 0.0 + if f < r(3)[0]: + u = suave(T.p(f, 2) / 0.6) if kk == 0 else 0.0 + ob.location.x = mezcla(fila[0] - 3.5, fila[0], u) + ob.hide_render = u <= 0.001 + elif g == "c" and f < suelta["c"]: + kk = int(k[1:]) + n = n_c(f) + n0 = int(math.floor(n)) + u = n - n0 + def pos(nn): + if kk >= nn: + return None + return pila(nn, C_TORRE)[kk], z_de(nn, kk) + p0, p1 = pos(n0), pos(min(N_C, n0 + 1)) + if p0 is None and p1 is None: + ob.hide_render = True + continue + if p0 is None: # el bloque nuevo entra por abajo desde la izquierda + p0 = (p1[0] - 3.0, p1[1]) + x = p0[0] + (p1[0] - p0[0]) * u if p1 else p0[0] + z = p0[1] + (p1[1] - p0[1]) * u if p1 else p0[1] + ob.location = (x, 0.0, z) + elif g == "x" and f < suelta["x"]: + u = suave((T.p(f, 8) - 0.15) / 0.45) + ob.hide_render = u <= 0.001 + ob.location.z = fila[2] + 2.0 * (1 - u) + + # dibujos + v_borde = T.p(f, 3) > 0 and f < r(9)[0] + borde.hide_render = not v_borde + for k, t in enumerate(etiquetas): + t.hide_render = not (r(3)[0] + (k * 0.17 + 0.08) * (r(3)[1] - r(3)[0]) <= f < r(6)[0]) + v_aire = r(4)[0] + 20 <= f < r(6)[0] + aire.hide_render = aire_t.hide_render = not v_aire + for k, e in enumerate(cms): + e.hide_render = not (r(5)[0] + k * 8 <= f < r(6)[0]) + v_c = r(6)[0] + 5 <= f < r(8)[1] + cuenta.hide_render = not v_c + n = n_c(f) + cuenta.data.body = f"{int(round(n))} bloques" + top_z = z_de(int(round(n)), 0) + H / 2 + cuenta.location = (-0.45, -0.3, top_z + 0.35) + v_regla = r(7)[0] <= f < r(8)[1] + regla.hide_render = sobre.hide_render = not v_regla + sobre.location = (1.2, -0.3, 2.98) + + cx, cz, h = camara_en(f) + dist = h * lente / 36.0 + zc = cz - 0.10 * h # el contenido queda arriba de los subtitulos + apuntar(cam, (cx - 0.16 * h, -dist, zc + 0.16 * h), (cx, 0, zc)) + + render_secuencia(NOMBRE, T, actualizar) + + +main() -- cgit v1.2.3