Acumulando Pontos 01

2026-08-07

"""2026-08-07
Acumulando Pontos 01
Usando polígonos regulares, criamos uma circunferência de pontos.
ericof.com|https://ericof.com/en/sketches/2023-09-03
png
Sketch,py5,CreativeCoding
"""

from sketches.padroes.poligonos import vertices_poligono_regular
from sketches.utils.draw import canvas
from sketches.utils.helpers import sketches as helpers

import numpy as np
import py5


sketch = helpers.info_for_sketch(__file__, __doc__)

cor_fundo = py5.color(0)

lados = 8
rotacao = 0.0

pontos_por_frame = 400
fator_atracao = 0.666
peso_ponto = 2
saturacao = (80, 100)
brilho = (80, 100)

vertices: np.ndarray
matizes: np.ndarray
camada: py5.Py5Graphics


def inicializa() -> None:
    """Calcula o polígono da área interna e cria a camada acumuladora.

    Os vértices vêm de :func:`vertices_poligono_regular` em coordenadas locais
    da área interna. O matiz de cada vértice é o seu ângulo (em graus) em
    relação ao centro do polígono.
    """
    global vertices, matizes, camada
    camada = py5.create_graphics(*helpers.DIMENSOES.internal, py5.P3D)
    with camada.begin_draw():
        camada.color_mode(py5.HSB, 360, 100, 100)
        camada.clear()
    largura, altura = camada.width, camada.height
    vertices = vertices_poligono_regular(largura, altura, lados, rotacao)
    deltas = vertices - np.array([largura / 2, altura / 2])
    matizes = np.degrees(np.arctan2(deltas[:, 1], deltas[:, 0])) % 360


def dentro_do_poligono(x: float, y: float) -> bool:
    """Verifica se um ponto está dentro do polígono convexo.

    :param x: Coordenada x, em coordenadas locais da área interna.
    :param y: Coordenada y, em coordenadas locais da área interna.
    :returns: ``True`` se o ponto estiver dentro do polígono.
    """
    arestas = np.roll(vertices, -1, axis=0) - vertices
    para_ponto = np.array([x, y]) - vertices
    cruzados = arestas[:, 0] * para_ponto[:, 1] - arestas[:, 1] * para_ponto[:, 0]
    return bool(np.all(cruzados >= 0) or np.all(cruzados <= 0))


def sorteia_ponto_interno() -> tuple[float, float]:
    """Sorteia um ponto uniforme dentro do polígono, por rejeição na bounding box.

    :returns: Par ``(x, y)`` em coordenadas locais da área interna.
    """
    x_min, y_min = vertices.min(axis=0)
    x_max, y_max = vertices.max(axis=0)
    while True:
        x = py5.random(x_min, x_max)
        y = py5.random(y_min, y_max)
        if dentro_do_poligono(x, y):
            return x, y


def acumula_pontos(quantidade: int) -> None:
    """Desenha na camada pontos atraídos para os vértices do polígono.

    Cada ponto nasce numa posição uniforme dentro do polígono e é deslocado
    ``fator_atracao`` do caminho até um vértice sorteado, herdando o matiz
    desse vértice com saturação e brilho aleatórios.

    :param quantidade: Número de pontos a acrescentar nesta chamada.
    """
    with camada.begin_draw():
        camada.stroke_weight(peso_ponto)
        for _ in range(quantidade):
            x0, y0 = sorteia_ponto_interno()
            idx = py5.random_int(0, lados - 1)
            x1, y1 = vertices[idx]
            cor = camada.color(
                float(matizes[idx]),
                py5.random(*saturacao),
                py5.random(*brilho),
            )
            camada.stroke(cor)
            x = float(py5.lerp(x0, x1, fator_atracao))
            y = float(py5.lerp(y0, y1, fator_atracao))
            camada.point(x, y)


def setup():
    py5.pixel_density(py5.display_density())
    py5.size(*helpers.DIMENSOES.external, py5.P3D)
    py5.color_mode(py5.HSB, 360, 100, 100)
    inicializa()
    acumula_pontos(pontos_por_frame)


def draw():
    py5.background(cor_fundo)
    acumula_pontos(pontos_por_frame)
    with py5.push():
        py5.translate(*helpers.DIMENSOES.pos_interno)
        if camada is not None:
            largura = helpers.DIMENSOES.internal[0] * py5.display_density()
            altura = helpers.DIMENSOES.internal[1] * py5.display_density()
            py5.image(camada, 0, 0, largura, altura)
    # Credits and go
    canvas.sketch_frame(
        sketch,
        cor_fundo,
        "large_transparent_white",
        "transparent_white",
        version=2,
    )


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


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


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