-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy path__main__.py
More file actions
246 lines (205 loc) · 7.49 KB
/
Copy path__main__.py
File metadata and controls
246 lines (205 loc) · 7.49 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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
"""2026-06-20
Caquinhos Redux 02
Homenagem aos pisos de caquinhos de São Paulo (Usando Voronoi).
ericof.com|https://bsky.app/profile/cefzanella.bsky.social/post/3mokxh3ae3s2o
png
Sketch,py5,CreativeCoding,Voronoi
"""
from opensimplex import OpenSimplex
from scipy.spatial import Voronoi
from sketches.utils.draw import canvas
from sketches.utils.draw.grade import cria_grade_ex
from sketches.utils.helpers import sketches as helpers
import math
import numpy as np
import py5
sketch = helpers.info_for_sketch(__file__, __doc__)
cor_fundo = py5.color(0, 0, 0)
cor_fundo_interna = py5.color(60, 60, 45)
celula_x = 20
celula_y = 20
mult_x = 15
mult_y = 15
borda = 0.15
faixa_bagunca = 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 = 100
noise_generator = OpenSimplex(seed=py5.random_int(20_000))
cacos: list[tuple[int, py5.Py5Shape]] = []
def par_bagunca(faixa_bagunca: int) -> tuple[float, float]:
"""Sorteia um deslocamento aleatorio em x e y dentro de uma faixa.
:param faixa_bagunca: Amplitude maxima do deslocamento em cada eixo.
:returns: Par ``(bx, by)`` de deslocamentos.
"""
bx = py5.random(-faixa_bagunca, faixa_bagunca)
by = py5.random(-faixa_bagunca, faixa_bagunca)
return bx, by
def cor_caco(idx: int, idy: int) -> int:
"""Escolhe a cor de um caco a partir dos indices de grade da sua semente.
:param idx: Indice da coluna na grade.
:param idy: Indice da linha na grade.
:returns: Cor do caco.
"""
total = idx + idy
cor = py5.color(152, 59, 47)
if total % 10 == 0:
cor = py5.color(0)
elif total % 7 == 0:
cor = py5.color(218, 160, 57)
return cor
def gera_sementes(
largura: int,
altura: int,
celula_x: int,
celula_y: int,
) -> tuple[list[tuple[float, float]], list[tuple[int, int]], int]:
"""Gera as sementes do diagrama de Voronoi.
As sementes internas vem de uma grade jitterada por noise e bagunca; em
seguida um anel de sementes-sentinela e adicionado fora da area interna
para garantir que toda celula visivel seja finita (fechada), dispensando
o recorte das regioes abertas do Voronoi.
:param largura: Largura da area interna.
:param altura: Altura da area interna.
:param celula_x: Passo horizontal da grade.
:param celula_y: Passo vertical da grade.
:returns: Tripla ``(pontos, indices, n_internas)`` onde as primeiras
``n_internas`` entradas de ``pontos`` sao as sementes visiveis e
``indices`` guarda o par ``(idx, idy)`` de cada uma para colorir.
"""
pontos: list[tuple[float, float]] = []
indices: list[tuple[int, int]] = []
for idx, x0, idy, y0 in cria_grade_ex(
largura, altura, 0, 0, celula_x, celula_y, True
):
noise = noise_generator.noise2(x0, y0)
bx, by = par_bagunca(faixa_bagunca)
pontos.append((x0 + mult_x * noise + bx, y0 + mult_y * noise + by))
indices.append((idx, idy))
n_internas = len(pontos)
passo = max(celula_x, celula_y)
for c in range(-margem_guarda, largura + margem_guarda + 1, passo):
pontos.append((c, -margem_guarda))
pontos.append((c, altura + margem_guarda))
for c in range(-margem_guarda, altura + margem_guarda + 1, passo):
pontos.append((-margem_guarda, c))
pontos.append((largura + margem_guarda, c))
return pontos, indices, n_internas
def cria_forma_caco(verts: list[tuple[float, float]], cor: int) -> py5.Py5Shape:
"""Monta a forma de um caco a partir dos vertices da sua celula de Voronoi.
Os vertices sao ordenados angularmente em torno do centroide (a celula e
convexa, logo a ordem angular descreve o poligono simples) e entao
encolhidos em direcao ao centroide pelo fator ``1 - borda``. A folga
resultante entre cacos vizinhos revela o fundo, simulando o rejunte.
:param verts: Vertices da celula de Voronoi.
:param cor: Cor de preenchimento do caco.
:returns: Forma pronta para ser desenhada.
"""
cx = sum(v[0] for v in verts) / len(verts)
cy = sum(v[1] for v in verts) / len(verts)
verts = sorted(verts, key=lambda v: math.atan2(v[1] - cy, v[0] - cx))
escala = 1 - borda
forma = py5.create_shape()
forma.set_fill(cor)
forma.set_stroke_weight(0)
with forma.begin_closed_shape():
for vx, vy in verts:
forma.vertex(cx + (vx - cx) * escala, cy + (vy - cy) * escala)
return forma
def popula_cacos(
largura: int,
altura: int,
celula_x: int,
celula_y: int,
) -> list[tuple[int, py5.Py5Shape]]:
"""Constroi os cacos tesselando a area interna com um diagrama de Voronoi.
:param largura: Largura da area interna.
:param altura: Altura da area interna.
:param celula_x: Passo horizontal da grade de sementes.
:param celula_y: Passo vertical da grade de sementes.
:returns: Lista de pares ``(z, forma)`` para desenho.
"""
pontos, indices, n_internas = gera_sementes(largura, altura, celula_x, celula_y)
vor = Voronoi(np.array(pontos))
cacos = []
for i in range(n_internas):
regiao = vor.regions[vor.point_region[i]]
if not regiao or -1 in regiao:
# Celula aberta: nao deve ocorrer para sementes internas gracas ao
# anel-sentinela, mas ignoramos por seguranca.
continue
verts = [tuple(vor.vertices[v]) for v in regiao]
idx, idy = indices[i]
forma = cria_forma_caco(verts, cor_caco(idx, idy))
cacos.append((py5.random_int(0, 5), forma))
return cacos
def inicializa():
global cacos
cacos = popula_cacos(*helpers.DIMENSOES.internal, celula_x, celula_y)
def setup():
py5.size(*helpers.DIMENSOES.external, py5.P3D)
inicializa()
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.internal)
def draw():
py5.background(cor_fundo)
with py5.push():
py5.translate(*helpers.DIMENSOES.pos_interno, -5)
desenha_fundo(cor_fundo_interna)
for z, forma in cacos:
with py5.push():
py5.translate(0, 0, z)
py5.shape(forma)
msg = (
f"celula (x - y): {celula_x} - {celula_y} | "
f"borda: {borda:.2f} | "
f"bagunca: {faixa_bagunca}"
)
# Credits and go
canvas.sketch_frame(
sketch,
cor_fundo,
"large_transparent_white",
"transparent_white",
version=2,
msg=msg,
)
def key_pressed():
global celula_x, celula_y, borda, faixa_bagunca
key = py5.key
match key:
case "r":
inicializa()
case ">":
celula_x += 2
celula_y += 2
inicializa()
case "<":
celula_x = max(4, celula_x - 2)
celula_y = max(4, celula_y - 2)
inicializa()
case "w":
borda = min(0.6, borda + 0.05)
inicializa()
case "s":
borda = max(0.0, borda - 0.05)
inicializa()
case "+":
faixa_bagunca += 5
inicializa()
case "-":
faixa_bagunca = max(0, faixa_bagunca - 5)
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()