# Genera docs/calle.svg: calle pintada que se disuelve, con «presencias» que, # juntas, forman una figura grande con el puño en alto (a la manera de Suso33). import sys, random, math random.seed(26) OUT = sys.argv[1] W, H = 1600, 900 VX, VY = 800, 70 # punto de fuga INK, ORA = "#111", "#ff7a1a" o = [] add = o.append def esc(y): # tamaño según la distancia return max(0.0, (y - VY) / (H - VY)) def edge(xb, y): # x de una recta que va del punto de fuga a (xb, H) return VX + (xb - VX) * esc(y) Y0 = 210 # lo de arriba queda en blanco: se recorta add(f'') add(''' ''') # --- calle pintada: todo dentro de la máscara que la disipa --- add('') road = [(VX, VY), (edge(-150, H), H), (edge(1750, H), H)] add(f'') def brush(x0, y0, x1, y1, w, n, col=INK, op=.85): for _ in range(n): j = lambda: random.uniform(-w, w) * .5 add(f'') add('') for xb, w in ((-150, 26), (1750, 26)): # bordillos y0 = VY + 40 brush(edge(xb, y0), y0, xb, H + 30, w, 7) add('') # --- figura grande: siluetas sencillas en coordenadas de la imagen --- def cap(px, py, ax, ay, bx, by, r): # cápsula (segmento con radio) dx, dy = bx - ax, by - ay t = max(0, min(1, ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy))) return math.hypot(px - (ax + t * dx), py - (ay + t * dy)) < r K, YT = .8, 250 # escala y altura de la figura def dentro(x, y): x, y = 800 + (x - 800) / K, 60 + (y - YT) / K return (math.hypot(x - 772, (y - 205) * .9) < 56 or # cabeza cap(x, y, 790, 372, 815, 560, 88) or # tronco cap(x, y, 735, 360, 590, 270, 34) or cap(x, y, 590, 270, 560, 150, 32) or # brazo en alto math.hypot(x - 556, y - 128) < 46 or # puño cap(x, y, 880, 360, 960, 470, 32) or cap(x, y, 960, 470, 935, 585, 30) or # brazo bajo cap(x, y, 770, 590, 700, 735, 44) or cap(x, y, 700, 735, 650, 870, 40) or # pierna cap(x, y, 860, 590, 940, 720, 44) or cap(x, y, 940, 720, 1010, 860, 40)) # pierna # --- presencias --- def presencia(x, y, s, col): h = 46 * s # alto de la figura r = h * random.uniform(.10, .13) # cabeza lean = random.uniform(-.12, .12) * h w = h * random.uniform(.13, .19) # hombros fx = x + lean # pies # sombra hacia abajo a la derecha L = h * random.uniform(.9, 1.3) add(f'') sx, sy = x, y - h + 2 * r # hombros split = random.random() < .35 # piernas abiertas al andar if split: d = (f'M{sx-w/2:.1f},{sy:.1f} Q{sx-w*.55:.1f},{sy+h*.35:.1f} {fx-w*.32:.1f},{y:.1f} ' f'L{fx-w*.12:.1f},{y:.1f} L{fx:.1f},{y-h*.3:.1f} L{fx+w*.12:.1f},{y:.1f} L{fx+w*.32:.1f},{y:.1f} ' f'Q{sx+w*.55:.1f},{sy+h*.35:.1f} {sx+w/2:.1f},{sy:.1f} Z') else: d = (f'M{sx-w/2:.1f},{sy:.1f} Q{sx-w*.45:.1f},{sy+h*.45:.1f} {fx-w*.1:.1f},{y:.1f} ' f'L{fx+w*.1:.1f},{y:.1f} Q{sx+w*.45:.1f},{sy+h*.45:.1f} {sx+w/2:.1f},{sy:.1f} Z') add(f'') add(f'') pts = [] def libre(x, y, s): return all(math.hypot(x - a, (y - b) * 1.6) > 17 * max(s, t) for a, b, t in pts) for _ in range(200000): # la figura grande, densa x, y = random.uniform(400, 1150), random.uniform(240, 890) if dentro(x, y): s = .62 + .38 * esc(y) if libre(x, y, s * 1.35): pts.append((x, y, s)) grande = len(pts) for _ in range(900): # poca gente suelta, por la calle y = random.uniform(300, 880) x = random.uniform(edge(-150, y), edge(1750, y)) s = .62 + .38 * esc(y) if not dentro(x, y) and libre(x, y, s * 5) and random.random() < .35: pts.append((x, y, s)) pts.sort(key=lambda p: p[1]) # de lejos a cerca add('') for x, y, s in pts: presencia(x, y, s, ORA if random.random() < .05 else INK) add('') open(OUT, "w").write("\n".join(o)) print(f"{len(pts)} presencias ({grande} en la figura grande)", file=sys.stderr)