Caquinhos Redux 06

2026-06-26

"""2026-06-26
Caquinhos Redux 06
Pisos de caquinhos de São Paulo, realidade alternativa.
ericof.com
png
Sketch,py5,CreativeCoding,Voronoi
"""

from opensimplex import OpenSimplex
from random import shuffle
from sketches.padroes.cacos import popula_cacos
from sketches.utils.draw import canvas
from sketches.utils.draw.grade import cria_grade
from sketches.utils.helpers import sketches as helpers
from sketches.utils.helpers.images import estencil_como_forma
from sketches.utils.helpers.images import resource_image_as_array
from sketches.utils.helpers.timing import report_time

import py5


sketch = helpers.info_for_sketch(__file__, __doc__)

cor_fundo = py5.color(226, 214, 187)
cor_fundo_interna = py5.color(60, 60, 45)

CORES_PESOS = (
    (py5.color(152, 59, 47), 93),
    (py5.color(218, 160, 57), 2),
    (py5.color(0), 3),
)

celula_x = 30
celula_y = 30
mult_x = 25
mult_y = 25
borda = 0.15
faixa_bagunca = 20
profundidade_caco = 6

# Ponto de luz controlado pelo mouse: x,y vem do cursor, altura (z) e tom sao
# fixos. A ambiente baixa evita que os cacos longe da luz fiquem pretos.
luz_altura = 800
luz_cor = py5.color(255, 244, 214)
luz_ambiente = py5.color(40, 40, 40)

# Distancia do anel de sementes-sentinela alem da area interna. Precisa ser
# maior que o deslocamento maximo de uma semente interna (mult + faixa_bagunca)
# para que toda semente visivel fique cercada e sua celula seja finita.
margem_guarda = 400

noise_generator = OpenSimplex(seed=py5.random_int(20_000))

cacos: list[py5.Py5Shape] = []


def cores_pesos() -> list[int]:
    """Expande ``CORES_PESOS`` numa lista com cada cor repetida conforme seu peso.

    A lista e embaralhada e serve de base para o sorteio ponderado de cores
    em :func:`cor_caco`.

    :returns: Lista de cores com repeticao proporcional ao peso de cada uma.
    """
    cores = []
    for cor, peso in CORES_PESOS:
        cores.extend([cor] * peso)
    shuffle(cores)
    return cores


CORES = cores_pesos()


def inicializa():
    global cacos
    grade = cria_grade(*helpers.DIMENSOES.external, 0, 0, celula_x, celula_y)
    with report_time("Calculando..."):
        cacos = popula_cacos(
            *helpers.DIMENSOES.external,
            celula_x,
            celula_y,
            grade,
            mult_x,
            mult_y,
            faixa_bagunca,
            margem_guarda,
            borda,
            profundidade_caco,
            noise_generator,
            CORES,
        )


def setup():
    global estencil
    py5.size(*helpers.DIMENSOES.external, py5.P3D)
    inicializa()
    mascara = resource_image_as_array("sao-paulo.png")
    estencil = estencil_como_forma(mascara, cor_fundo)


def desenha_fundo(cor: int):
    """Desenha o fundo por baixo dos caquinhos."""
    with py5.push():
        py5.fill(cor)
        py5.no_stroke()
        py5.rect(0, 0, *helpers.DIMENSOES.external)


def draw():
    py5.background(cor_fundo)
    # Posicao da luz no espaco interno: o mouse e screen-space, entao
    # descontamos a origem do painel (pos_interno).
    luz_x = py5.mouse_x - helpers.DIMENSOES.pos_interno[0]
    luz_y = py5.mouse_y - helpers.DIMENSOES.pos_interno[1]
    with py5.push():
        py5.translate(0, 0, -15)
        # Fundo desenhado antes das luzes para manter o rejunte chapado; os
        # prismas se assentam nele (base em z=0) e sobem em direcao a camera.
        desenha_fundo(cor_fundo_interna)
        # Luz pontual seguindo o mouse + ambiente baixa, no mesmo espaco dos
        # cacos. Atuam so aqui; o no_lights() abaixo protege o overlay.
        py5.ambient_light(
            py5.red(luz_ambiente), py5.green(luz_ambiente), py5.blue(luz_ambiente)
        )
        py5.point_light(
            py5.red(luz_cor),
            py5.green(luz_cor),
            py5.blue(luz_cor),
            luz_x,
            luz_y,
            luz_altura,
        )
        for forma in cacos:
            py5.shape(forma)
    py5.no_lights()

    with py5.push():
        py5.translate(*helpers.DIMENSOES.pos_interno, 0)
        py5.shape(estencil)

    msg = (
        f"celula (x - y): {celula_x} - {celula_y} | "
        f"borda: {borda:.2f} | "
        f"bagunca: {faixa_bagunca} | "
        f"prof: {profundidade_caco} | "
        f"luz (x - y): {luz_x} - {luz_y}"
    )
    # Credits and go
    canvas.sketch_frame(
        sketch,
        cor_fundo,
        "large_transparent",
        "transparent",
        version=2,
        msg=msg,
    )


def key_pressed():
    global celula_x, celula_y, borda, faixa_bagunca, profundidade_caco
    key = py5.key
    match key:
        case "r":
            inicializa()
        case ">" | "<":
            passo = 2 if key == ">" else -2
            celula_x = max(4, celula_x + passo)
            celula_y = max(4, celula_y + passo)
            inicializa()
        case "w" | "s":
            borda = min(0.6, max(0.0, borda + (0.05 if key == "w" else -0.05)))
            inicializa()
        case "+" | "-":
            faixa_bagunca = max(0, faixa_bagunca + (5 if key == "+" else -5))
            inicializa()
        case "e" | "d":
            profundidade_caco = max(0, profundidade_caco + (1 if key == "e" else -1))
            inicializa()
        case " ":
            save_and_close()


def save_and_close():
    py5.no_loop()
    canvas.save_sketch_image(sketch)
    py5.exit_sketch()


if __name__ == "__main__":
    py5.run_sketch()