# Genera docs/calle.svg: una calle en perspectiva dibujada solo con líneas negras # (bordillos, pie de fachada, portales, línea del centro) y, encima, una piña de # monigotes de un solo trazo de spray en zigzag, de colores: fuego en el centro y # fríos alrededor (a la manera de Suso33). import sys, random, math random.seed(4) OUT = sys.argv[1] W, H = 1600, 680 VX, VY, F, EYE = 1180, 110, 520, 5.0 # punto de fuga, focal, ojo en un primer piso (m) FACH, KI, KD = -7.0, -4.5, 3.6 # fachada, bordillo izquierdo y derecho (m) INK = "#111" FUEGO = ["#fffbe6", "#ffe066", "#ffb627", "#ff7b1c", "#f04e23", "#c8102e"] # del centro hacia fuera FRIO = ["#5b2a86", "#8e44c9", "#3b5bdb", "#6c7ae0", "#b07cd8", "#2a9d8f", "#111"] o = [] add = o.append def P(X, Yh, Z): return VX + X * F / Z, VY + (EYE - Yh) * F / Z def linea(a, b, w, op=.95): add(f'') Y0 = 100 # arriba solo hay blanco: se recorta add(f'') add(''' ''') # --- la calle: solo líneas negras a pincel --- ZN, ZF = 3.0, 90 add('') linea(P(FACH, 0, ZN), P(FACH, 0, ZF), 7) # pie de la fachada linea(P(KI, 0, ZN), P(KI, 0, ZF), 9) # bordillo linea(P(KI + .18, 0, ZN), P(KI + .18, 0, ZF), 3, .7) # canto del bordillo linea(P(KD, 0, 5), P(KD, 0, ZF), 6, .8) # bordillo de enfrente z = 4.5 while z < 60: # línea discontinua del centro linea(P(-.3, 0, z), P(-.3, 0, z + 2.2), 2 + 16 / z, .8) z += 5 for zd in (4.0, 7.5, 11.5, 17.5, 26.0, 38.0): # portales: solo el contorno a, b = P(FACH, 0, zd), P(FACH, 0, zd + 1.5) c, d = P(FACH, 2.5, zd), P(FACH, 2.5, zd + 1.5) w = 30 / zd + 2 linea(a, c, w); linea(c, d, w); linea(d, b, w) linea(P(FACH, 2.8, zd - .3), P(FACH, 2.8, zd + 1.8), w * .6, .6) # dintel add('') # --- monigotes de un trazo --- def monigote(x, y, h, col, lean=0.0, paso=False, brazo=False): sw = max(1.1, h * .011) dy = sw * 2.0 top = y - h pts, yy, i = [], top, 0 while yy <= y: t = (yy - top) / h cx = x + lean * h * (1 - t) if t < .13: # cabeza w = h * .05 * math.sin(math.pi * t / .13) ** .6 + sw * .3 elif t < .16: # cuello w = h * .008 else: # cuerpo que se afila hacia los pies u = (t - .16) / .84 w = h * (.062 * (1 - u) ** .8 + .014) if paso and u > .5: # zancada: el zigzag se abre en dos cx += (-1 if (i // 2) % 2 else 1) * h * .035 * (u - .5) / .5 w *= .6 pts.append((cx + (-1 if i % 2 else 1) * w + random.uniform(-.3, .3) * sw, yy)) yy += dy * random.uniform(.85, 1.15) * (.55 if t < .13 else 1) i += 1 d = "M" + " L".join(f"{px:.1f},{py:.1f}" for px, py in pts) add(f'') if brazo: # brazo en alto bx, by = x + lean * h * .84 + h * .05, top + h * .2 p = [(bx + j / 9 * h * .06 + (-1 if j % 2 else 1) * h * .012, by - j / 9 * h * .30) for j in range(10)] d = "M" + " L".join(f"{px:.1f},{py:.1f}" for px, py in p) add(f'') # reparto en metros sobre el suelo: la piña por la calzada y regueros desde los portales CX, CZ, R = -.4, 8.5, 2.8 # centro de la piña (fuego) y radio gente = [] def cabe(X, Z): return all(math.hypot(X - a, (Z - b) * .8) > .5 for a, b in gente) for _ in range(14000): X, Z = random.gauss(-.2, 2.4), random.gauss(9.5, 4.2) if KI - 1.5 < X < KD - .3 and 4.9 < Z < 32 and cabe(X, Z): gente.append((X, Z)) for zd in (7.5, 11.5, 17.5): # salen de los portales hacia la calzada for k in range(4): X, Z = FACH + .6 + k * .7, zd + .75 - k * .5 if cabe(X, Z): gente.append((X, Z)) add(f'') for X, Z in sorted(gente, key=lambda g: -g[1]): # de lejos a cerca x, y = P(X, 0, Z) h = 1.7 * F / Z * random.uniform(.9, 1.08) dist = math.hypot(X - CX, Z - CZ) / R if dist < 1: col = FUEGO[min(len(FUEGO) - 1, int(max(0, dist + random.uniform(-.12, .12)) * len(FUEGO)))] else: col = random.choice(FRIO) monigote(x, y, h, col, random.uniform(-.06, .04), random.random() < .35, random.random() < .1) add('') open(OUT, "w").write("\n".join(o)) print(f"{len(gente)} monigotes", file=sys.stderr)