# Genera la portada (la plaza que nace del cruce del boceto 5) y los bocetos 6 a 10: # plazas y cruces con el mismo acabado a mano que tools/bocetos.py. import sys, random, math, os sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from bocetos import Calle, INK, FUEGO, FRIO, LUCHAS EJES = ["#f04e23", "#14965a", "#f2b705", "#3b5bdb", "#8e44c9"] class Escena(Calle): def zn(s): return s.EYE * s.F / (s.H + s.Y0 - s.VY) * .8 def seg(s, a, b, w=None, op=.95): zn = s.zn() if a[2] < zn and b[2] < zn: return if a[2] > b[2]: a, b = b, a if a[2] < zn: t = (zn - a[2]) / (b[2] - a[2]) a = tuple(a[i] + (b[i] - a[i]) * t for i in range(3)) if w is None: w = 34 / ((a[2] + b[2]) / 2) + 1.6 s.linea(s.P(*a), s.P(*b), w, op) def poli(s, pts, w=None, op=.95): for a, b in zip(pts, pts[1:]): s.seg(a, b, w, op) def abrir(s, *mascaras): for m in mascaras: s.o.append(f'') s.o.append('') s.cierre = '' + '' * len(mascaras) def cerrar(s): s.o.append(s.cierre) def portal(s, a, b, alto=2.5, cornisa=True): s.poli([(a[0], 0, a[1]), (a[0], alto, a[1]), (b[0], alto, b[1]), (b[0], 0, b[1])]) if cornisa: dx, dz = b[0] - a[0], b[1] - a[1] s.seg((a[0] - dx * .2, alto + .35, a[1] - dz * .2), (b[0] + dx * .2, alto + .35, b[1] + dz * .2), None, .6) def fachada(s, a, b, portales=(), alto=2.5): s.seg((a[0], 0, a[1]), (b[0], 0, b[1])) L = math.hypot(b[0] - a[0], b[1] - a[1]) ux, uz = (b[0] - a[0]) / L, (b[1] - a[1]) / L for d in portales: s.portal((a[0] + ux * d, a[1] + uz * d), (a[0] + ux * (d + 1.5), a[1] + uz * (d + 1.5)), alto) def esquina(s, X, Z, alto=3.2): s.seg((X, 0, Z), (X, alto, Z), None, .7) def farola(s, X, Z, h=4.2): s.seg((X, 0, Z), (X, h, Z), None, .9) s.seg((X, h, Z), (X + .6, h - .1, Z), None, .9) c = s.P(X + .6, h - .35, Z) s.o.append(f'') def tienda(s, X, Z, col, a=2.0, f=2.2, alto=1.2): x0, x1, xm = X - a / 2, X + a / 2, X fr = [(x0, 0, Z), (xm, alto, Z), (x1, 0, Z)] s.poli(fr, None, .9) s.seg((xm, alto, Z), (xm, alto, Z + f), None, .6) s.seg((x0, 0, Z), (x0, 0, Z + f), None, .5) s.seg((x1, 0, Z), (x1, 0, Z + f), None, .5) p = [s.P(*q) for q in fr] d = f"M{p[0][0]:.1f},{p[0][1]:.1f} L{p[1][0]:.1f},{p[1][1]:.1f} L{p[2][0]:.1f},{p[2][1]:.1f} Z" s.tiendas.append(f'') def gente(s, pina, regueros, extra=(), puños=.1, zona=None, zmax=40, ejes=None): CX, CZ, sx, sz, n, R = pina zmin = s.EYE * s.F / (s.H + s.Y0 - s.VY) + .3 zona = zona or (lambda X, Z: True) g = [] def cabe(X, Z): return all(math.hypot(X - a, (Z - b) * .8) > .42 + .022 * Z for a, b, _ in g) for X0, Z0, col, k in regueros: for j in range(k): t = (j + .5) / k X = X0 + (CX - X0) * t + random.uniform(-.3, .3) Z = Z0 + (CZ - Z0) * t + random.uniform(-.3, .3) if Z > zmin and cabe(X, Z): g.append((X, Z, col)) for _ in range(n): X, Z = random.gauss(CX, sx), random.gauss(CZ, sz) if zmin < Z < zmax and zona(X, Z) and cabe(X, Z): g.append((X, Z, None)) g += list(extra) c = s.P(CX, 1.0, CZ) r0 = 2600 / CZ s.o.append(f'') for _ in range(700): ang, rr = random.uniform(0, 2 * math.pi), abs(random.gauss(0, r0 * .55)) k = min(len(FUEGO) - 1, int(rr / r0 * len(FUEGO))) s.o.append(f'') s.o += s.tiendas for X, Z, col in sorted(g, key=lambda q: -q[1]): x, y = s.P(X, 0, Z) h = 1.7 * s.F / Z * random.uniform(.9, 1.08) d = math.hypot(X - CX, Z - CZ) / R if d < 1: col = FUEGO[min(len(FUEGO) - 1, int(max(0, d + random.uniform(-.12, .12)) * len(FUEGO)))] elif col is None: col = random.choice(ejes or FRIO) if ejes and random.random() < .35 else random.choice(FRIO) s.monigote(x, y, h, col, random.uniform(-.06, .04), random.random() < .35, random.random() < puños) return len(g) def __init__(s, **k): super().__init__(**k) s.tiendas = [] def llegan(X0, Z0, col, n): return (X0, Z0, col, n) def portada(): # El cruce del boceto 5 se abre en una plaza: la calle llega desde abajo y la gente entra por todas partes random.seed(15) c = Escena(VX=1000, VY=60, EYE=9.0, F=600, Y0=100) FA, FD, KI, KD = -6.5, 6.6, -4.2, 3.6 z1, z2, xl, xr = 11.0, 25.0, -24.0, 17.0 c.abrir("lados", "arriba") c.fachada((FA, 3.0), (FA, z1), (4.0,)) c.fachada((FD, z1), (FD, 3.0), (3.0,)) c.seg((KI, 0, 3.0), (KI, 0, z1 - 1), 9) c.seg((KI + .18, 0, 3.0), (KI + .18, 0, z1 - 1), 3, .7) c.seg((KD, 0, 3.0), (KD, 0, z1 - 1), 7, .8) for z in (4.0, 9.0): c.seg((-.3, 0, z), (-.3, 0, z + 2.2), None, .8) c.esquina(FA, z1); c.esquina(FD, z1) c.fachada((FA, z1), (xl, z1), (6.0, 12.0)) c.fachada((xr, z1), (FD, z1), (4.0,)) c.fachada((xl, z1), (xl, 15.5), ()) c.fachada((xl, 19.5), (xl, z2), ()) for z in (15.5, 19.5): c.esquina(xl, z) c.seg((xl, 0, z), (-70, 0, z)) c.fachada((xr, z2), (xr, z1), (3.0, 9.0)) c.fachada((xl, z2), (-4.5, z2), (4.0, 10.0, 15.0)) c.fachada((4.5, z2), (xr, z2), (3.0, 8.0)) for x in (-4.5, 4.5): c.esquina(x, z2) c.seg((x, 0, z2), (x, 0, 90)) c.esquina(xl, z1); c.esquina(xr, z1); c.esquina(xl, z2); c.esquina(xr, z2) for x, z in ((-8, 13.5), (8, 13.5), (-8, 22.5), (8, 22.5)): c.farola(x, z) c.cerrar() c.tienda(-19.5, 12.5, "#14965a") c.tienda(-16.0, 21.0, "#f2b705") c.tienda(13.0, 12.8, "#3b5bdb") c.tienda(13.5, 20.0, "#8e44c9") zona = lambda X, Z: (KI - 1.4 < X < KD - .3 and Z < z1) or (xl + .8 < X < xr - .8 and z1 + .5 < Z < z2 - .5) or (abs(X) < 4 and Z >= z2) c.gente((-1.0, 16.0, 3.4, 2.8, 4200, 3.4), [llegan(FA + .5, 4.75, LUCHAS[0], 6), llegan(-60, 17.5, EJES[1], 14), llegan(-.3, 60, EJES[3], 12), llegan(xr - 1, 16, EJES[2], 6), llegan(.3, 3.2, EJES[0], 9)], zona=zona, ejes=EJES, puños=.14, zmax=80) return c def b6(): # La plaza: de frente, fachadas en U y la gente llenándola random.seed(21) c = Escena(VX=800, VY=120, EYE=6.0, F=560, Y0=80) zf, xl, xr = 26.0, -15.0, 15.0 c.abrir("lados", "arriba") c.fachada((xl, 3.0), (xl, zf), (5.0, 10.0, 15.0)) c.fachada((xr, zf), (xr, 3.0), (4.0, 9.0, 14.0)) c.fachada((xl, zf), (-4.0, zf), (2.0, 6.0)) c.fachada((4.0, zf), (xr, zf), (2.5, 6.5)) for x in (-4.0, 4.0): c.esquina(x, zf) c.seg((x, 0, zf), (x, 0, 70)) c.esquina(xl, zf); c.esquina(xr, zf) for x in (-10.0, 10.0): c.farola(x, 9.0); c.farola(x, 20.0) c.cerrar() c.gente((0, 15, 3.2, 2.6, 3500, 3.0), [llegan(0, 40, LUCHAS[0], 10), llegan(xl + .6, 8.0, LUCHAS[1], 5), llegan(xr - .6, 7.0, LUCHAS[3], 5)], zona=lambda X, Z: xl + .8 < X < xr - .8 and Z < zf - .5 or abs(X) < 3.5, ejes=EJES, puños=.15) return c def b7(): # Dos calles que se cruzan en X: de cada esquina llega un eje con su color random.seed(22) c = Escena(VX=800, VY=110, EYE=7.0, F=560, Y0=80) cz, a = 16.0, 4.5 r = math.sqrt(.5) def W(u, v): return ((u - v) * r, (u + v) * r + cz) c.abrir("lados", "arriba") for su in (-1, 1): for sv in (-1, 1): p0 = W(su * a, sv * a) pu = W(su * 60, sv * a) pv = W(su * a, sv * 60) c.esquina(*p0) for q in (pu, pv): c.seg((p0[0], 0, p0[1]), (q[0], 0, q[1])) for d in (6.0, 12.0): for (u, v) in ((su * (a + d), sv * a), (su * a, sv * (a + d))): x, z = W(u, v) x2, z2 = W(u + su * 1.5 * (v == sv * a), v + sv * 1.5 * (u == su * a)) c.portal((x, z), (x2, z2)) for k in range(-8, 9, 3): for u, v in ((k, 0), (0, k)): if abs(k) > a + 1: p, q = W(u, v), W(u + (1.6 if v == 0 else 0), v + (1.6 if u == 0 else 0)) c.seg((p[0], 0, p[1]), (q[0], 0, q[1]), None, .7) c.cerrar() llegadas = [] for (u, v), col in zip(((-30, 0), (30, 0), (0, -9), (0, 30)), EJES): x, z = W(u, v) llegadas.append(llegan(x, z, col, 12)) zona = lambda X, Z: abs((X / r + (Z - cz) / r) / 2) < a - .3 or abs(((Z - cz) / r - X / r) / 2) < a - .3 c.gente((0, cz, 1.8, 1.8, 2500, 2.6), llegadas, zona=zona, puños=.15) return c def b8(): # La acampada: tiendas de colores en la plaza y la asamblea en el centro random.seed(23) c = Escena(VX=800, VY=120, EYE=6.5, F=560, Y0=80) zf, xl, xr = 28.0, -16.0, 16.0 c.abrir("lados", "arriba") c.fachada((xl, 3.0), (xl, zf), (6.0, 12.0, 18.0)) c.fachada((xr, zf), (xr, 3.0), (5.0, 11.0, 17.0)) c.fachada((xl, zf), (xr, zf), (4.0, 9.0, 14.0, 19.5, 25.0)) c.esquina(xl, zf); c.esquina(xr, zf) c.farola(-6, 10); c.farola(6, 10); c.farola(0, 24) c.cerrar() random.seed(230) tiendas = [] for fila, z in enumerate((10.5, 13.0, 22.0, 24.8)): for x in range(-13, 14, 4): if abs(x) > 5 or z > 20: tiendas.append((x + random.uniform(-.6, .6) + (fila % 2) * 2, z + random.uniform(-.4, .4))) for k, (x, z) in enumerate(sorted(tiendas, key=lambda t: -t[1])): c.tienda(x, z, EJES[k % len(EJES)]) lejos = lambda X, Z: all(math.hypot(X - x, Z - (z + 1.1)) > 1.6 for x, z in tiendas) c.gente((0, 17, 2.6, 1.8, 3000, 2.6), [], zona=lambda X, Z: xl < X < xr and Z < zf - .5 and lejos(X, Z), ejes=EJES, puños=.2) return c def b9(): # Sol: cinco calles llegan a la misma plaza, cada una con su reguero random.seed(24) c = Escena(VX=800, VY=120, EYE=8.0, F=560, Y0=80) cz, R = 22.0, 11.0 angs = [-70, -35, 0, 35, 70] c.abrir("lados", "arriba") pts = [] for k in range(-90, 91, 6): t = math.radians(k) pts.append((R * math.sin(t), cz + R * math.cos(t) * .9)) huecos = [math.radians(a) for a in angs] tramo = [] for k, (x, z) in zip(range(-90, 91, 6), pts): dentro = any(abs(math.radians(k) - h) < math.radians(8) for h in huecos) if dentro: if len(tramo) > 1: s0 = [(px, 0, pz) for px, pz in tramo] c.poli(s0) tramo = [] else: tramo.append((x, z)) if len(tramo) > 1: c.poli([(px, 0, pz) for px, pz in tramo]) for h in huecos: for d in (-1, 1): t = h + d * math.radians(8) x0, z0 = R * math.sin(t), cz + R * math.cos(t) * .9 x1, z1 = x0 + math.sin(h) * 40, z0 + math.cos(h) * 40 c.esquina(x0, z0) c.seg((x0, 0, z0), (x1, 0, z1)) c.seg((-R, 0, cz), (-R, 0, 4)); c.seg((R, 0, cz), (R, 0, 4)) c.farola(0, cz - 3) c.cerrar() llegadas = [llegan(math.sin(math.radians(a)) * (R + 14), cz + math.cos(math.radians(a)) * (R + 14) * .9, col, 10) for a, col in zip(angs, EJES)] c.gente((0, cz - 1, 2.8, 2.6, 3500, 3.0), llegadas, zona=lambda X, Z: math.hypot(X, (Z - cz) / .9) < R - .6 or Z < cz, ejes=EJES, puños=.15, zmax=60) return c def b10(): # Desde el balcón: la plaza vista desde arriba, la piña como una hoguera de gente random.seed(25) c = Escena(VX=800, VY=40, EYE=16.0, F=520, Y0=100) zf, xl, xr = 34.0, -18.0, 18.0 c.abrir("lados") c.fachada((xl, 8.0), (xl, zf), (8.0, 16.0)) c.fachada((xr, zf), (xr, 8.0), (6.0, 14.0)) c.fachada((xl, zf), (-4.0, zf), (3.0, 9.0)) c.fachada((4.0, zf), (xr, zf), (3.0, 9.0)) for x in (-4.0, 4.0): c.seg((x, 0, zf), (x, 0, 80)) for x, z in ((-12, 14), (12, 14), (-12, 28), (12, 28)): c.farola(x, z) c.cerrar() c.gente((0, 21, 3.6, 3.2, 4500, 3.8), [llegan(0, 60, EJES[3], 12), llegan(xl + .6, 12, EJES[1], 6), llegan(xr - .6, 30, EJES[2], 6)], zona=lambda X, Z: (xl + .8 < X < xr - .8 and Z < zf - .5) or abs(X) < 3.5, ejes=EJES, puños=.12, zmax=70) return c if __name__ == "__main__": bocetos, plaza = sys.argv[1:3] os.makedirs(bocetos, exist_ok=True) for n, f in zip(range(6, 11), [b6, b7, b8, b9, b10]): open(f"{bocetos}/boceto-{n}.svg", "w").write(f().svg()) open(plaza, "w").write(portada().svg())