Waves VI

2024-04-02

"""2024-04-02
Waves VI
Série de ondas, em tons de verde, criadas com funções trigonométricas.
png
Sketch,py5,CreativeCoding,waves
"""

from collections import defaultdict
from typing import List, Tuple

import numpy as np
import py5

from utils import helpers

sketch = helpers.info_for_sketch(__file__, __doc__)

MARGEM_X = 280
MARGEM_Y = 180


def calcula_circulo(
    largura: int, altura: int, margem_x: int, margem_y: int
) -> List[Tuple[float, float]]:
    """Calcula circulo que toque as margens."""
    raio_x = (largura - (2 * margem_x)) / 2
    raio_y = (altura - (2 * margem_y)) / 2
    x0 = margem_x + raio_x
    y0 = margem_y + raio_y
    pontos = []
    for angulo in range(0, 360):
        angulo_radianos = py5.radians(angulo)
        x = x0 + (py5.cos(angulo_radianos) * raio_x)
        y = y0 + (py5.sin(angulo_radianos) * raio_y)
        pontos.append((x, y))
    return pontos


def desenha_borda(h: int, margem_y: int) -> dict:
    """Desenha a borda para o sketch e retorna os limites para y."""
    circulo = calcula_circulo(py5.width, py5.height, MARGEM_X, MARGEM_Y)
    pontos_y = defaultdict(set)
    with py5.push_style():
        py5.stroke_weight(6)
        py5.stroke(h, 60, 60)
        for x, y in circulo:
            py5.point(x, y)
            pontos_y[int(y)].add(x)
    limites = {k: (min(v), max(v)) for k, v in pontos_y.items()}
    ymin = margem_y
    ymax = py5.height - margem_y
    for y in range(ymin, ymax + 1):
        limites_y = limites.get(y)
        if limites_y:
            continue
        for diff in (0, -1, 1, -2, 2, -3, 3):
            limites_y = limites[y + diff] if y + diff in limites else None
            if limites_y:
                limites[y] = limites_y
                break
    return limites


def setup():
    py5.size(helpers.LARGURA, helpers.ALTURA, py5.P3D)
    py5.background(252, 249, 230)
    py5.color_mode(py5.HSB, 360, 100, 100)
    py5.no_fill()
    py5.stroke_weight(2)
    h = 160
    limites_y = desenha_borda(h, MARGEM_Y)
    passos = 40
    lx = py5.width
    ly = py5.height
    x = np.linspace(0, lx, endpoint=False, num=lx)
    y = np.linspace(10, ly, endpoint=False, num=passos)
    base_multi = np.logspace(0.4, 1.6, num=int(passos / 2), endpoint=False)
    multiplicadores = sorted(base_multi) + sorted(base_multi, reverse=True)
    pesos = np.linspace(4, 2, num=passos, endpoint=False)
    y = zip(y, multiplicadores, pesos)
    for yb, mult_b, peso in y:
        y0 = None
        ybi = int(yb)
        limites = limites_y.get(ybi, [py5.width + 100, py5.width + 100])
        xi = limites[0]
        x0 = xi
        xf = limites[1]
        py5.stroke_weight(peso)
        s = py5.remap(mult_b, 0, 40, 60, 100)
        for x1 in x:
            if x1 >= xi and x1 <= xf:
                x0 = xf
                continue
            xd = py5.remap(x1, 0, 800, -60, 60)
            mult = mult_b * py5.cos(py5.radians(xd))
            y1 = yb + (
                mult * (abs(py5.cos(py5.radians(x0 + (4.1 * yb))) * py5.sin(1.4 * x1)))
            )
            b = py5.remap(xd, -60, 60, 80, 60)
            py5.stroke(h, s, b)
            if y0 is None:
                y0 = y1
            py5.line(x0, y0, x1, y1)
            x0, y0 = x1, y1
    helpers.write_legend(sketch=sketch, cor=py5.color(h, s, b))


def key_pressed():
    key = py5.key
    if key == " ":
        save_and_close()


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


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