Petals 02

2026-04-22

"""2026-04-22
Petals 02
Interpretação de pôr do sol sobre o oceano, usando círculos para criar a ilusão de ondas e céu.
ericof.com
png
Sketch,py5,CreativeCoding
"""  # noqa: E501

from collections.abc import Sequence
from sketches.utils.draw import canvas
from sketches.utils.helpers import sketches as helpers

import py5


sketch = helpers.info_for_sketch(__file__, __doc__)

cor_fundo = py5.color(0)
cor_fundo_interna = py5.color(255)
z_interna = -10

extensao_min: int = 100
extensao_max: int = 700
passos_total: int = 6
cor_petala: int = py5.color("#EDD70D", 40)


def calcula_ext_pos(
    largura: int,
    extensao_min: int = extensao_min,
    extensao_max: int = extensao_max,
    total: int = passos_total,
    reverso: bool = False,
) -> Sequence[tuple[float, float]]:
    """Calcula pares ``(x, extensao)`` para uma sequência de círculos crescentes.

    :param largura: Largura disponível, em pixels.
    :param extensao_min: Diâmetro do menor círculo da sequência.
    :param extensao_max: Diâmetro do maior círculo da sequência.
    :param total: Número de passos entre o menor e o maior círculo.
    :param reverso: Se ``True``, espelha a sequência a partir da borda direita.
    :returns: Pares ``(x, extensao)`` para cada círculo.
    """
    passo = (extensao_max - extensao_min) / total
    x0 = (largura - extensao_max) / 2 - (extensao_min / 2)
    if reverso:
        x0 = largura - x0
    pares = []
    extensao: float = extensao_min
    x = x0 + extensao
    while extensao <= extensao_max:
        raio = extensao / 2
        x_diff = -raio - extensao_min if reverso else raio + extensao_min
        pares.append((x, extensao))
        extensao += passo
        x = x0 + x_diff
    return pares


def desenha_fundo(extensao: int = extensao_max):
    """Desenha o disco branco de fundo interno, atrás das camadas de círculos.

    :param extensao: Diâmetro do disco de fundo.
    """
    with py5.push():
        py5.translate(*helpers.DIMENSOES.centro, z_interna - 1)
        py5.ellipse_mode(py5.CENTER)
        py5.no_stroke()


def desenha_circulos(pares: Sequence[tuple[float, float]], cor: int, direcao: int = 1):
    """Desenha uma camada de meio-círculos sobre a área interna do sketch.

    :param pares: Pares ``(x, extensao)`` produzidos por :func:`calcula_ext_pos`.
    :param cor: Cor de preenchimento; o stroke é derivado com alpha reduzido.
    :param direcao: ``1`` para a metade superior, ``-1`` para a metade inferior.
    """
    with py5.push():
        traco = py5.color(
            py5.red(cor), py5.green(cor), py5.blue(cor), py5.alpha(cor) * 0.5
        )
        py5.fill(cor)
        py5.stroke(traco)
        py5.stroke_weight(2)
        for x, extensao in pares:
            py5.circle(x, 0, extensao)


def setup():
    """Inicializa o canvas P3D e pré-calcula as sequências de pares (x, extensao)."""
    global pares, pares_reverso
    py5.size(*helpers.DIMENSOES.external, py5.P3D)
    py5.color_mode(py5.RGB)
    pares = calcula_ext_pos(helpers.DIMENSOES.internal[0], total=passos_total)


def draw():
    """Loop principal: compõe céu, oceano e aplica a moldura de créditos."""
    py5.background(cor_fundo)
    desenha_fundo()
    with py5.push():
        py5.translate(*helpers.DIMENSOES.centro, 0)
        for _ in range(6):
            py5.rotate(py5.radians(60))
            desenha_circulos(pares, cor_petala, 1)

    # Credits and go
    canvas.sketch_frame(
        sketch,
        cor_fundo,
        "large_transparent_white",
        "transparent_white",
        version=2,
    )


def key_pressed():
    """Salva e encerra o sketch ao pressionar a barra de espaço."""
    key = py5.key
    if key == " ":
        save_and_close()


def save_and_close():
    """Para o loop, grava o PNG final e encerra o sketch."""
    py5.no_loop()
    canvas.save_sketch_image(sketch)
    py5.exit_sketch()


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