-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy path__main__.py
More file actions
196 lines (159 loc) · 5.48 KB
/
Copy path__main__.py
File metadata and controls
196 lines (159 loc) · 5.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
"""2026-03-19
Manchas concêntricas 02
Distribuição de círculos concêntricos
ericof.com
png
Sketch,py5,CreativeCoding
"""
from collections import deque
from dataclasses import dataclass
from sketches.utils.draw import canvas
from sketches.utils.draw.cores import extrai_rgba
from sketches.utils.draw.cores import lerp_color_rgba
from sketches.utils.draw.cores.paletas import gera_paleta
from sketches.utils.draw.cores.paletas import lista_paletas
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)
paletas: deque[list[int]] = deque([
gera_paleta("op-2363803") for name in lista_paletas() if name.startswith("op-")
])
@dataclass
class CirculoRaio:
raio: float
traco: float
cor: int
@dataclass
class CirculosConcentricos:
x: float
y: float
z: float
circulos: list[CirculoRaio]
CIRCULOS: list[CirculosConcentricos] = []
def calcula_raios(
num: int,
cor_1: int,
cor_2: int,
z_dist: float,
inicial: float,
final: float,
) -> list[CirculoRaio]:
"""Considerando valores para raio inicial e final, retorna lista com num CirculoRaio
* Primeiramente calculamos a distância média entre os raios
* Iteramos por range(num)
* raio será a distância média + random_int
* traco será (distância média /2) * random(0.2, 0.75)
"""
raios = []
dist_media = (final - inicial) / num
traco_base = dist_media / 1.2
t_minmax = (0, num)
for idx in range(num):
r, g, b, a = extrai_rgba(lerp_color_rgba(cor_1, cor_2, idx, t_minmax))
a *= z_dist
cor = py5.color(r, g, b, a)
raios.append(
CirculoRaio(
raio=(dist_media + py5.random_int(-3, 3)) * idx + inicial,
cor=cor,
traco=traco_base * py5.random(0.2, 0.75),
)
)
return raios
def calcula_circulos(
x: float,
y: float,
z: float,
z_dist: float,
r_range: tuple[float, float],
total: int,
cor_1: int,
cor_2: int,
) -> CirculosConcentricos:
circulos = calcula_raios(total, cor_1, cor_2, z_dist, *r_range)
return CirculosConcentricos(x=x, y=y, z=z, circulos=circulos)
def calcula_pontos(
total: int,
x_range: tuple[float, float],
y_range: tuple[float, float],
z_range: tuple[float, float],
coeficiente: int = 6,
) -> tuple[tuple[tuple[float, float, float], ...], tuple[float, float]]:
"""Retorna uma tupla con `total` coordenadas (x, y, z).
Os pontos são distribuidos, inicialmente em uma matrix 2d de
maneira equidistante (respeitando os limites estipulados em x_range
e y_range).
Para cada ponto, os valores de x e y devem ter aplicado algum `noise`
para que a distribuição dos pontos seja levemente "bagunçada"
A coordenada z será calculada utilizando o py5.random(*z_range)
"""
cols = int(np.ceil(np.sqrt(total)))
rows = int(np.ceil(total / cols))
xs = np.linspace(x_range[0], x_range[1], cols)
ys = np.linspace(y_range[0], y_range[1], rows)
grid_x, grid_y = np.meshgrid(xs, ys)
step_x = (x_range[1] - x_range[0]) / cols
# Desloca meio passo em x nas linhas ímpares
offset = np.where(np.arange(rows) % 2 == 1, step_x / 2, 0.0)
grid_x += offset[:, np.newaxis]
flat_x = grid_x.flatten()[:total]
flat_y = grid_y.flatten()[:total]
step_y = (y_range[1] - y_range[0]) / rows
flat_x += np.random.uniform(-step_x / coeficiente, step_x / coeficiente, total)
flat_y += np.random.uniform(-step_y / coeficiente, step_y / coeficiente, total)
zs = np.array([py5.random(*z_range) for _ in range(total)])
return tuple(zip(flat_x, flat_y, zs, strict=True)), (step_x, step_y)
def setup():
py5.size(*helpers.DIMENSOES.external, py5.P3D)
meio_x = helpers.DIMENSOES.external[0] / 2
meio_y = helpers.DIMENSOES.external[1] / 2
x_range = -meio_x, meio_x + 1
y_range = -meio_y, meio_y + 1
z_range = -900, 40
pontos, (passo_x, passo_y) = calcula_pontos(400, x_range, y_range, z_range)
for x, y, z in pontos:
paleta = paletas[0]
passo = max([passo_x, passo_y])
r_max = py5.random((passo), passo * 2)
r_range = py5.random(r_max / 3.0, r_max / 1.8), r_max
total = py5.random_int(5, 14)
cor_1 = py5.random_choice(paleta)
cor_2 = py5.random_choice(paleta)
while cor_1 == cor_2:
cor_2 = py5.random_choice(paleta)
z_dist = float(py5.remap(z, z_range[0], z_range[1], 0, 1))
CIRCULOS.append(calcula_circulos(x, y, z, z_dist, r_range, total, cor_1, cor_2))
paletas.rotate()
def draw():
py5.background("#010101")
with py5.push():
py5.translate(*helpers.DIMENSOES.centro, -40)
py5.no_fill()
for figura in CIRCULOS:
for info in figura.circulos:
with py5.push():
py5.translate(0, 0, figura.z)
raio = info.raio
py5.stroke_weight(info.traco)
py5.stroke(info.cor)
py5.circle(figura.x, figura.y, raio)
# 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()