diff options
Diffstat (limited to 'blender/domino.py')
| -rw-r--r-- | blender/domino.py | 539 |
1 files changed, 539 insertions, 0 deletions
diff --git a/blender/domino.py b/blender/domino.py new file mode 100644 index 0000000..bf5f681 --- /dev/null +++ b/blender/domino.py @@ -0,0 +1,539 @@ +# -*- coding: utf-8 -*- +"""DOMINO - una ficha de 5 mm, cada una 1,5 veces mas grande que la anterior. + +Parte real: 13 fichas (5 mm a 65 cm) simuladas en Godot (Jolt), a escala real. +Parte imaginada: las fichas 14 a 36 en pie, comparadas con una persona, el +Obelisco, el Empire State y el Aconcagua. Las alturas son exactas: + h(n) = 5 mm * 1,5^(n-1) + + MODO=sim blender -b -P blender/domino.py # exporta y corre Godot + PRUEBA=1 (con MODO=sim) simula sin timeline, para ajustar la separacion + blender -b -P blender/domino.py # renderiza +""" +import math, os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import bpy +from base import * + +NOMBRE = "domino" +H0 = 0.005 # la primera ficha +R = 1.5 # cada una, 1,5 veces la anterior +N_REAL = 13 +N_TOTAL = 36 +ANCHO, GROSOR = 0.50, float(os.environ.get("T_GR", 0.10)) # proporciones de la ficha respecto del alto +SEP = float(os.environ.get("T_SEP", 0.70)) # hueco entre fichas, en altos de la anterior +FRIC = float(os.environ.get("T_FR", 0.7)) +FPS_SIM = 240 # se graba a 240 fps: permite la camara lenta x8 + + +def alto(n): + return H0 * R ** (n - 1) + + +def posiciones(n_max=N_TOTAL): + """Centro en x de cada ficha (1-based: xs[1] es la primera).""" + xs = [0.0, 0.0] + for n in range(1, n_max): + h, h2 = alto(n), alto(n + 1) + xs.append(xs[-1] + GROSOR * h / 2 + SEP * h + GROSOR * h2 / 2) + return xs + + +def construir(): + escena(muestras=int(os.environ.get("MUESTRAS", 24))) + mundo_estudio(fuerza=0.6) + xs = posiciones() + M = { + "ficha": material("ficha", "#15171C", rug=0.18), + "punto": material("punto", "#F2F2F2", rug=0.3), + "piso": madera("piso", claro="#8C6239", oscuro="#4A2E18", escala=3.0, rug=0.75, + veta=(0.5, 1, 1)), + "inv": material("inv", "gris"), + } + fichas = {} + for n in range(1, N_TOTAL + 1): + h = alto(n) + nombre = f"bloque_d{n:02d}" if n <= N_REAL else f"ficha_{n:02d}" + ob = caja(nombre, (GROSOR * h, ANCHO * h, h), (xs[n], 0, h / 2), M["ficha"]) + # los puntos del domino, en la cara que mira a la camara (-X) y la opuesta + for lado in (-1, 1): + for (py, pz) in pips(n): + p = esfera(f"pip_{n}_{lado}_{py}_{pz}", 0.055 * h, M["punto"], seg=12, anillos=6) + p.scale = (0.35, 1, 1) + p.location = (lado * GROSOR * h / 2, py * h, pz * h) + p.parent = ob + raya = caja(f"raya_{n}", (GROSOR * h * 1.02, ANCHO * h * 0.8, 0.012 * h), (0, 0, 0), + M["punto"]) + raya.parent = ob + fichas[n] = ob + inclinar(fichas[1], xs[1], INCL) + piso = caja("caja_piso", (4.0, 3.0, 0.2), (1.0, 0, -0.1), M["inv"]) + piso.hide_render = True + return dict(fichas=fichas, xs=xs, M=M, piso=piso) + + +INCL = math.radians(12.0) # la primera arranca pasada su inclinacion de vuelco (9 grados) + + +def inclinar(ob, x, ang): + """Gira la ficha sobre su canto delantero de abajo (el que mira a +X).""" + h = ob.dimensions.z + t = ob.dimensions.x + c, s_ = math.cos(ang), math.sin(ang) + ob.rotation_euler = (0, ang, 0) + ob.location = (x + t / 2 - c * t / 2 + s_ * h / 2, 0, s_ * t / 2 + c * h / 2) + + +def pips(n): + """Puntos (y, z relativos al alto) de un domino: dos mitades con 0..6.""" + pat = {0: [], 1: [(0, 0)], 2: [(-1, 1), (1, -1)], 3: [(-1, 1), (0, 0), (1, -1)], + 4: [(-1, 1), (1, 1), (-1, -1), (1, -1)], + 5: [(-1, 1), (1, 1), (0, 0), (-1, -1), (1, -1)], + 6: [(-1, 1), (1, 1), (-1, 0), (1, 0), (-1, -1), (1, -1)]} + a, b = (n * 5) % 7, (n * 3 + 2) % 7 + out = [] + for (cy, cz), val in (((0, 0.25), a), ((0, -0.25), b)): + for (dy, dz) in pat[val]: + out.append((cy + dy * 0.13, cz + dz * 0.13)) + return out + + +def cuadro_suelta(T): + """El lapiz la empuja al final del beat 3 ("le doy un toquecito...").""" + a, z = T.rango(3) + return a + int(0.78 * (z - a)) + + +def simular(t_suelta, duracion, obj): + reales = [obj["fichas"][n] for n in range(1, N_REAL + 1)] + cfg = { + "duracion": duracion, + "gravedad": 9.81, + "escala": 40.0, + "hz": 7680, + "fps": FPS_SIM, + "congelados": ["bloque_"], + "reglas": {"bloque_": {"friccion": FRIC, "rebote": 0.0, "densidad": 1100}, + "caja_": {"friccion": FRIC, "rebote": 0.0}}, + "eventos": [{"t": t_suelta, "accion": "soltar", "prefijo": "bloque_"}], + } + correr_godot(exportar_fisica(NOMBRE, reales + [obj["piso"]], cfg)) + + +# --- escenografia visual (no participa de la fisica) --------------------------- +def gris(nombre, col, rug=0.6): + return material(nombre, col, rug=rug) + + +def persona(x, y): + m = gris("persona", "#C9D1DC") + g = bpy.data.objects.new("persona", None) + bpy.context.collection.objects.link(g) + partes = [] + for sx in (-0.1, 0.1): + c = cilindro(f"pierna{sx}", 0.075, 0.85, m); c.location = (0, sx, 0.425); partes.append(c) + t = cilindro("torso", 0.19, 0.62, m); t.location = (0, 0, 1.17); partes.append(t) + for sy in (-0.26, 0.26): + b = cilindro(f"brazo{sy}", 0.055, 0.62, m); b.location = (0, sy, 1.15); partes.append(b) + cab = esfera("cabeza", 0.12, m); cab.location = (0, 0, 1.62); partes.append(cab) + for p_ in partes: + p_.parent = g + g.location = (x, y, 0) + return g + + +def obelisco(x, y): + m = gris("obelisco", "#EDEFF2", 0.5) + b0, b1, h = 6.8 / 2, 3.5 / 2, 63.5 + v = [(sx * b0, sy * b0, 0) for sx, sy in ((-1, -1), (1, -1), (1, 1), (-1, 1))] + v += [(sx * b1, sy * b1, h) for sx, sy in ((-1, -1), (1, -1), (1, 1), (-1, 1))] + v += [(0, 0, 67.5)] + f = [(0, 1, 5, 4), (1, 2, 6, 5), (2, 3, 7, 6), (3, 0, 4, 7), (4, 5, 8), (5, 6, 8), + (6, 7, 8), (7, 4, 8), (3, 2, 1, 0)] + ob = objeto("obelisco", malla_de("obelisco", v, f, suave=False), m) + ob.location = (x, y, 0) + return ob + + +def empire(x, y): + m = gris("empire", "#AEB6C2", 0.45) + g = bpy.data.objects.new("empire", None) + bpy.context.collection.objects.link(g) + for (w, d, z0, z1) in ((130, 58, 0, 25), (95, 50, 25, 250), (70, 42, 250, 290), + (48, 34, 290, 330), (26, 26, 330, 381)): + c = caja(f"es{z0}", (w, d, z1 - z0), (0, 0, (z0 + z1) / 2), m) + c.parent = g + a = cilindro("antena", 2.5, 62, m); a.location = (0, 0, 381 + 31); a.parent = g + g.location = (x, y, 0) + return g + + +def montana(x, y, alto_m=6961.0, radio=9000.0): + """Cono con ruido; nieve arriba por un material que depende de la altura.""" + import random + rnd = random.Random(5) + n_r, n_a = 40, 96 + v, f = [(0, 0, alto_m)], [] + fase = [rnd.uniform(0, 6.28) for _ in range(4)] + for i in range(1, n_r + 1): + r = radio * i / n_r + for j in range(n_a): + a = 2 * math.pi * j / n_a + ruido = sum(0.10 / (k + 1) * math.sin((k + 2) * a + fase[k] + 0.0007 * r * (k + 1)) + for k in range(4)) + z = alto_m * max(0.0, 1 - r / radio) ** 1.25 * (1 + ruido * (i / n_r)) + v.append((r * math.cos(a), r * math.sin(a), max(0.0, z))) + for j in range(n_a): + f.append((0, 1 + j, 1 + (j + 1) % n_a)) + for i in range(n_r - 1): + for j in range(n_a): + a0 = 1 + i * n_a + j + a1 = 1 + i * n_a + (j + 1) % n_a + f.append((a0, a0 + n_a, a1 + n_a, a1)) + m = bpy.data.materials.new("roca") + m.use_nodes = True + nt = m.node_tree + b = nt.nodes.get("Principled BSDF") + tc = nt.nodes.new("ShaderNodeTexCoord") + sep = nt.nodes.new("ShaderNodeSeparateXYZ") + mr = nt.nodes.new("ShaderNodeMapRange") + mr.inputs[1].default_value = alto_m * 0.62 + mr.inputs[2].default_value = alto_m * 0.72 + rampa = nt.nodes.new("ShaderNodeValToRGB") + rampa.color_ramp.elements[0].color = (*srgb("#5A5048"), 1) + rampa.color_ramp.elements[1].color = (*srgb("#F4F6FA"), 1) + nt.links.new(tc.outputs["Object"], sep.inputs[0]) + nt.links.new(sep.outputs["Z"], mr.inputs[0]) + nt.links.new(mr.outputs[0], rampa.inputs[0]) + nt.links.new(rampa.outputs["Color"], b.inputs["Base Color"]) + poner(b, "Roughness", 0.8) + ob = objeto("montana", malla_de("montana", v, f), m) + ob.location = (x, y, 0) + return ob + + +def casa(x, y): + m = gris("casa", "#D8C7A8", 0.7) + mt = gris("techo", "#8C3B2E", 0.6) + g = bpy.data.objects.new("casa", None) + bpy.context.collection.objects.link(g) + c = caja("casa_c", (9, 7, 5), (0, 0, 2.5), m); c.parent = g + v = [(-4.8, -3.8, 5), (4.8, -3.8, 5), (4.8, 3.8, 5), (-4.8, 3.8, 5), (-4.8, 0, 8), (4.8, 0, 8)] + f = [(0, 1, 5, 4), (2, 3, 4, 5), (0, 4, 3), (1, 2, 5), (3, 2, 1, 0)] + t = objeto("techo", malla_de("techo", v, f, suave=False), mt); t.parent = g + g.location = (x, y, 0) + return g + + +def moneda(x, y): + m = metal("moneda", "#D8B45A", rug=0.25) + c = cilindro("moneda", 0.0115, 0.0022, m, lados=48) + c.location = (x, y, 0.0011) + return c + + +def lapiz(): + g = bpy.data.objects.new("lapiz", None) + bpy.context.collection.objects.link(g) + cu = cilindro("lapiz_cuerpo", 0.0035, 0.12, material("amarillo", "#F2B61B", rug=0.4), lados=6) + cu.location = (0, 0, 0.06 + 0.012) + cu.parent = g + # punta: cono de madera con grafito + v = [(0.0035 * math.cos(2 * math.pi * k / 12), 0.0035 * math.sin(2 * math.pi * k / 12), 0.012) + for k in range(12)] + [(0, 0, 0)] + f = [(k, (k + 1) % 12, 12) for k in range(12)] + pt = objeto("lapiz_punta", malla_de("lapiz_punta", v, f), material("madera_l", "#E8C79A")) + pt.parent = g + return g + + +def main(): + obj = construir() + if os.environ.get("MODO") == "sim" and os.environ.get("PRUEBA"): + simular(0.2, 7.0, obj) + return + T = Tiempo(NOMBRE) + if os.environ.get("MODO") == "sim": + simular((cuadro_suelta(T) - 1) / FPS, T.n_frames / FPS + 0.5, obj) + return + render(T, obj) + + +def render(T, obj): + import numpy as np + idx, D = cargar_sim(NOMBRE) + fichas, xs = obj["fichas"], obj["xs"] + r = T.rango + fr = cuadro_suelta(T) + t_suelta = (fr - 1) / FPS + i0 = int(round(t_suelta * FPS_SIM)) + + # cuando cae cada ficha (inclinacion > 30 grados), en segundos desde la suelta + caida = {} + for n in range(1, N_REAL + 1): + qw = np.abs(D[:, idx[f"bloque_d{n:02d}"], 3]) + ang = 2 * np.degrees(np.arccos(np.clip(qw, 0, 1))) + k = int(np.argmax(ang > 30)) if (ang > 30).any() else len(D) - 1 + caida[n] = (k - i0) / FPS_SIM + t_cadena = caida[N_REAL] + 0.6 + print(f"[{NOMBRE}] caidas: " + " ".join(f"{n}:{caida[n]:.2f}" for n in caida)) + + # las imaginarias arrancan despues de donde quedo acostada la 13 + x_img = {N_REAL + 1: xs[N_REAL] + 1.25 * alto(N_REAL) + GROSOR * alto(N_REAL + 1)} + for n in range(N_REAL + 1, N_TOTAL): + h, h2 = alto(n), alto(n + 1) + x_img[n + 1] = x_img[n] + GROSOR * h / 2 + SEP * h + GROSOR * h2 / 2 + for n in range(N_REAL + 1, N_TOTAL + 1): + fichas[n].location.x = x_img[n] + X = dict(xs=xs) + X.update({n: xs[n] for n in range(1, N_REAL + 1)}) + X.update(x_img) + + # escenografia + mesa = caja("mesa", (6.0, 3.0, 0.02), (1.2, 0.0, -0.01), obj["M"]["piso"]) + mesa.visible_shadow = False # si no, su sombra cae sobre el suelo 2 mm mas abajo + # el suelo se escala cada cuadro segun la distancia de la camara: un plano de + # 200 km fijo tiene triangulos tan grandes que la profundidad pierde + # precision y tapa a la mesa (que esta 2 mm mas arriba) + suelo = caja("suelo", (1.0, 1.0, 0.02), (0, 0, -0.012), + material("suelo", "#2B2622", rug=0.9)) + lap = lapiz() + # comparaciones: la camara mira en la direccion VH (del lado de la cara con + # puntos) y cada referencia va al costado de su ficha, perpendicular a la + # mirada: asi estan a la misma distancia de la camara y la perspectiva no + # agranda a ninguna de las dos. + VH = (0.857, 0.514) + DER = (0.514, -0.857) # "a la derecha" en pantalla + L = {} + for n, gap, wl, hl in ((16, 0.30, 0.25, 1.75), (25, 12.0, 3.4, 67.5), + (29, 60.0, 65.0, 443.0), (36, 600.0, 4500.0, 6961.0), + (20, 2.5, 4.8, 8.0)): + dd = 0.3 * alto(n) + gap + wl + L[n] = (X[n] + DER[0] * dd, DER[1] * dd, dd, wl, hl) + pers = persona(L[16][0], L[16][1]) + obe = obelisco(L[25][0], L[25][1]) + emp = empire(L[29][0], L[29][1]) + mon_t = montana(L[36][0], L[36][1], radio=4500.0) + cas = casa(L[20][0], L[20][1]) + for ob_ in (pers, emp, cas): + ob_.rotation_euler = (0, 0, math.atan2(VH[1], VH[0])) # de frente a la camara + + def etiqueta(txt, col, tam, loc, giro=-0.5): + t = texto(txt, tam=tam, color=col) + t.location = loc + t.rotation_euler = (math.pi / 2, 0, giro) + t.visible_shadow = False + return t + + E = { + "5mm": etiqueta("5 mm", "ambar", 0.0035, (xs[1], -0.006, alto(1) + 0.004)), + "x15": etiqueta("×1,5", "ambar", 0.005, ((xs[1] + xs[2]) / 2, -0.01, alto(2) + 0.006)), + "65": etiqueta("65 cm", "ambar", 0.08, (xs[13], -0.1, alto(13) + 0.09)), + "16": etiqueta("16", "ambar", 0.40, (X[16], 0, 1.07 * alto(16) + 0.40 * 0.5), -1.03), + "vos": etiqueta("vos", "blanco", 0.26, (L[16][0], L[16][1], 2.0), -1.03), + "25": etiqueta("25", "ambar", 11, (X[25], 0, 1.07 * alto(25) + 11 * 0.5), -1.03), + "obe": etiqueta("Obelisco", "blanco", 7, (L[25][0], L[25][1], 75), -1.03), + "29": etiqueta("29", "ambar", 55, (X[29], 0, 1.07 * alto(29) + 55 * 0.5), -1.03), + "emp": etiqueta("Empire State", "blanco", 38, (L[29][0], L[29][1], 490), -1.03), + "36": etiqueta("36", "ambar", 900, (X[36], 0, 1.07 * alto(36) + 900 * 0.5), -1.03), + "aco": etiqueta("Aconcagua", "blanco", 700, (L[36][0], L[36][1], 7700), -1.03), + "casa": etiqueta("casa: 8 m", "blanco", 1.1, (L[20][0], L[20][1], 9.3), -1.03), + } + xs15 = [etiqueta("×1,5", "ambar", 0.35 * alto(n), (xs[n], -0.1 * alto(n), 0.22 * alto(n) + 0.02)) + for n in range(2, N_REAL + 1)] + + lente = 50.0 + cam = camara((0, -1, 0.3), (0, 0, 0), lente=lente) + cam.data.sensor_fit = 'VERTICAL' + cam.data.sensor_height = 36.0 + sol = bpy.data.lights.new("sol", 'SUN') + sol.energy = 3.2 + sol.angle = math.radians(3) + so = bpy.data.objects.new("sol", sol) + bpy.context.collection.objects.link(so) + so.rotation_euler = (math.radians(50), math.radians(-18), math.radians(-35)) + + def frente(t): + """Indice continuo de la ficha que esta cayendo a los t s de soltar.""" + if t <= 0: + return 1.0 + for n in range(1, N_REAL): + if t < caida[n + 1]: + a, b = caida[n], caida[n + 1] + return n + max(0.0, min(1.0, (t - a) / max(1e-6, b - a))) + return float(N_REAL) + + def cam_frente(t): + u = frente(t) + n = int(u) + w = u - n + n2 = min(N_REAL, n + 1) + x = X[n] + (X[n2] - X[n]) * w + 0.9 * alto(n) * (1 + 0.5 * w) + hh = alto(n) * R ** w + return x, 0.45 * hh, 5.5 * hh + + VISTA13 = ((xs[1] + xs[13]) / 2 + 0.25, 0.32, 2.3) + + def toma_ref(n): + """Encuadre de la ficha n con su referencia a la derecha: (x, z, alto, y).""" + h = alto(n) + _, _, dd, wl, hl = L[n] + izq = -(1.0 if n == 20 else 0.7) * h # algo de las fichas anteriores + der = dd + wl + c = (izq + der) / 2 + alto_v = max(max(h, hl) * 1.75, (der - izq) / 0.5625 * 1.3) + return (X[n] + DER[0] * c, max(h, hl) * 0.50, alto_v, DER[1] * c) + + tomas = [ + (1, (xs[1] + 0.003, 0.004, 0.032)), + (r(0)[1], (xs[1] + 0.004, 0.004, 0.034)), + (r(1)[0] + 20, ((xs[1] + xs[2]) / 2, 0.0055, 0.05)), + (r(1)[1], ((xs[1] + xs[2]) / 2 + 0.002, 0.006, 0.055)), + (r(2)[1] - 10, VISTA13), + (r(3)[0] + int(0.45 * (r(3)[1] - r(3)[0])), VISTA13), + (fr - 6, (xs[1] + 0.003, 0.004, 0.032)), + ] + f_rep0 = r(5)[0] + f_rep1 = r(6)[1] + tomas2 = [ + (f_rep1 + 1, VISTA13), + (r(8)[1], (VISTA13[0] + 0.1, 0.3, 2.0)), + (r(9)[1], (X[15] - 1.0, 1.0, 6.0)), + (r(10)[0] + 10, toma_ref(16)), + (r(10)[1], toma_ref(16)), + (r(11)[0] + 12, toma_ref(25)), + (r(11)[1], toma_ref(25)), + (r(12)[0] + 12, toma_ref(29)), + (r(12)[1], toma_ref(29)), + (r(13)[0] + 14, toma_ref(36)), + (r(13)[1], toma_ref(36)), + (r(14)[1] - 5, (xs[1] + 0.003, 0.004, 0.032)), + (r(15)[1], VISTA13), + (r(16)[1], (VISTA13[0], 0.3, 1.9)), + (r(17)[0] + 15, toma_ref(20)), + (T.n_frames, toma_ref(20)), + ] + + def interp(lista, f): + for (f0, a), (f1, b) in zip(lista, lista[1:]): + if f0 <= f <= f1: + u = suave((f - f0) / max(1, f1 - f0)) + ha, hb = a[2], b[2] + if max(ha, hb) / min(ha, hb) > 3: + # zoom grande: alto en escala log, el blanco se mueve con el alto + h = math.exp(math.log(ha) + (math.log(hb) - math.log(ha)) * u) + w = (h - ha) / (hb - ha) + else: + h = ha + (hb - ha) * u + w = u + ya = a[3] if len(a) > 3 else 0.0 + yb = b[3] if len(b) > 3 else 0.0 + return (a[0] + (b[0] - a[0]) * w, a[1] + (b[1] - a[1]) * w, h, ya + (yb - ya) * w) + ult = lista[-1][1] if f > lista[-1][0] else lista[0][1] + return tuple(ult) + ((0.0,) if len(ult) == 3 else ()) + + def t_sim(f): + """Segundos de simulacion desde la suelta que se muestran en el cuadro f.""" + if f < fr: + return 0.0 + if f < f_rep0: + return (f - fr) / FPS + if f <= f_rep1: + u = (f - f_rep0) / max(1, f_rep1 - f_rep0) + return t_cadena * u * u # rampa: lenta al principio + return t_cadena + 2.0 + + def actualizar(f): + ts = t_sim(f) + i = max(0, min(len(D) - 1, i0 + int(round(ts * FPS_SIM)))) + for n in range(1, N_REAL + 1): + poner_pose(fichas[n], D[i, idx[f"bloque_d{n:02d}"]]) + # antes de soltar, la primera esta derecha y el lapiz la inclina + f_emp = fr - 22 + if f < fr: + u = suave((f - f_emp) / 22.0) if f >= f_emp else 0.0 + fichas[1].rotation_mode = 'XYZ' + inclinar(fichas[1], xs[1], INCL * u) + h1 = alto(1) + lap_vis = (fr - 60) <= f <= fr + 25 + lap.hide_render = not lap_vis + for c in lap.children: + c.hide_render = not lap_vis + if lap_vis: + llega = suave((f - (fr - 60)) / 38.0) + vuelve = suave((f - fr) / 20.0) + u = suave((f - f_emp) / 22.0) if f >= f_emp else 0.0 + tip_x = xs[1] - GROSOR * h1 / 2 + h1 * math.sin(INCL * u) - 0.02 * (1 - llega) - 0.01 * vuelve + lap.location = (tip_x, 0.0, 0.9 * h1 + 0.02 * (1 - llega) + 0.01 * vuelve) + lap.rotation_euler = (0, math.radians(-62), 0) + + # imaginarias + # hasta que ficha se ve: crece con la narracion + if f < r(9)[0]: + n_vis = N_REAL + elif f < r(10)[0]: + n_vis = N_REAL + 3 * T.p(f, 9) + elif f < r(11)[0]: + n_vis = 16 + elif f < r(12)[0]: + n_vis = 16 + 9 * suave(T.p(f, 11) / 0.2) + elif f < r(13)[0]: + n_vis = 25 + 4 * suave(T.p(f, 12) / 0.2) + elif f < r(17)[0]: + n_vis = 29 + 7 * suave(T.p(f, 13) / 0.2) + else: + n_vis = 20 + for n in range(N_REAL + 1, N_TOTAL + 1): + fichas[n].hide_render = n > n_vis + 1e-6 + for c in fichas[n].children: + c.hide_render = fichas[n].hide_render + for ob_, beat in ((pers, 10), (obe, 11), (emp, 12), (mon_t, 13), (cas, 17)): + vis = f >= r(beat)[0] - 6 if beat != 17 else f >= r(17)[0] - 10 + ob_.hide_render = not vis + for c in ob_.children: + c.hide_render = not vis + vis_lab = { + "5mm": r(0)[0] <= f < r(2)[0] + 10, "x15": r(1)[0] + 15 <= f < r(2)[0] + 10, + "65": r(3)[0] <= f < fr - 30, "16": r(10)[0] <= f < r(11)[0], + "vos": r(10)[0] + 8 <= f < r(11)[0], "25": r(11)[0] <= f < r(12)[0], + "obe": r(11)[0] + 8 <= f < r(12)[0], "29": r(12)[0] <= f < r(13)[0], + "emp": r(12)[0] + 8 <= f < r(13)[0], "36": r(13)[0] <= f < r(14)[0], + "aco": r(13)[0] + 8 <= f < r(14)[0], "casa": f >= r(17)[0] + 10, + } + for k, t in E.items(): + t.hide_render = not vis_lab[k] + for j, t in enumerate(xs15): + t.hide_render = not (r(7)[0] + j * 4 <= f < r(9)[0]) + + # camara + y = 0.0 + if fr - 6 <= f < f_rep0 or f_rep0 <= f <= f_rep1: + x, z, h = cam_frente(ts) + if f < fr + 4: + a = interp(tomas, f) + w = suave((f - (fr - 6)) / 10.0) + x, z, h = (a[0] + (x - a[0]) * w, a[1] + (z - a[1]) * w, a[2] + (h - a[2]) * w) + elif f < fr - 6: + x, z, h, y = interp(tomas, f) + else: + x, z, h, y = interp(tomas2, f) + dist = h * lente / 36.0 + # angulo: alto y cruzado para las fichas chicas, mas de frente para las comparaciones + k = suave((f - r(9)[0]) / 20.0) * (1 - suave(T.p(f, 14))) + suave((f - r(17)[0]) / 15.0) + k = max(0.0, min(1.0, k)) + da, db = (-0.447, -0.894, 0.32), (-VH[0], -VH[1], 0.10) + d = tuple(da[j] + (db[j] - da[j]) * k for j in range(3)) + nd = math.sqrt(sum(c * c for c in d)) + zc = z - 0.08 * h # deja lugar a los subtitulos + pos = (x + d[0] / nd * dist, y + d[1] / nd * dist, zc + d[2] / nd * dist) + apuntar(cam, pos, (x, y, zc)) + lado = max(12.0, dist * 60.0) + suelo.scale = (lado, lado, 1.0) + suelo.location = (x, y, -0.012) + cam.data.clip_start = max(1e-5, dist * 0.002) + cam.data.clip_end = dist * 400 + + render_secuencia(NOMBRE, T, actualizar) + + +main() |