fractex 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fractex/3d.py +1585 -0
- fractex/__init__.py +38 -0
- fractex/advanced.py +170 -0
- fractex/cli.py +81 -0
- fractex/core.py +508 -0
- fractex/dynamic_textures_3d.py +1935 -0
- fractex/examples/3d.py +109 -0
- fractex/examples/3d_integration.py +113 -0
- fractex/examples/3d_integration_2d.py +59 -0
- fractex/examples/__init__.py +34 -0
- fractex/examples/_output.py +115 -0
- fractex/examples/architecture_pattern.py +61 -0
- fractex/examples/atmosphere.py +54 -0
- fractex/examples/composite_material.py +63 -0
- fractex/examples/crystal_cave.py +61 -0
- fractex/examples/custom_pattern.py +114 -0
- fractex/examples/game_integration.py +86 -0
- fractex/examples/game_texture.py +178 -0
- fractex/examples/integration.py +102 -0
- fractex/examples/physic_integration.py +70 -0
- fractex/examples/splash.py +159 -0
- fractex/examples/terrain.py +76 -0
- fractex/examples/underwater.py +94 -0
- fractex/examples/underwater_volkano.py +112 -0
- fractex/geometric_patterns_3d.py +2372 -0
- fractex/interactive.py +158 -0
- fractex/simplex_noise.py +1113 -0
- fractex/texture_blending.py +1377 -0
- fractex/volume_scattering.py +1263 -0
- fractex/volume_textures.py +8 -0
- fractex-0.1.0.dist-info/METADATA +100 -0
- fractex-0.1.0.dist-info/RECORD +36 -0
- fractex-0.1.0.dist-info/WHEEL +5 -0
- fractex-0.1.0.dist-info/entry_points.txt +2 -0
- fractex-0.1.0.dist-info/licenses/LICENSE +21 -0
- fractex-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,2372 @@
|
|
|
1
|
+
# fractex/geometric_patterns_3d.py
|
|
2
|
+
"""
|
|
3
|
+
Система генерации геометрических 3D паттернов
|
|
4
|
+
Кристаллы, соты, решетки, фрактальные структуры, математические поверхности
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
from typing import Dict, List, Tuple, Optional, Union, Callable
|
|
9
|
+
from numba import jit, prange, vectorize, float32, float64, int32, int64, complex128
|
|
10
|
+
import warnings
|
|
11
|
+
import math
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from enum import Enum
|
|
14
|
+
import itertools
|
|
15
|
+
from functools import lru_cache
|
|
16
|
+
try:
|
|
17
|
+
from scipy import spatial, ndimage
|
|
18
|
+
except ImportError: # optional dependency
|
|
19
|
+
spatial = None
|
|
20
|
+
ndimage = None
|
|
21
|
+
|
|
22
|
+
# ----------------------------------------------------------------------
|
|
23
|
+
# Структуры данных и перечисления
|
|
24
|
+
# ----------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
class GeometricPattern3D(Enum):
|
|
27
|
+
"""Типы геометрических 3D паттернов"""
|
|
28
|
+
CRYSTAL_LATTICE = 1 # Кристаллические решетки
|
|
29
|
+
HONEYCOMB = 2 # Сотовые структуры
|
|
30
|
+
GYROID = 3 # Гироидные поверхности
|
|
31
|
+
DIAMOND_STRUCTURE = 4 # Алмазная структура
|
|
32
|
+
SPHERE_PACKING = 5 # Упаковка сфер
|
|
33
|
+
VORONOI_CELLS = 6 # Ячейки Вороного
|
|
34
|
+
WEIRELEN_PIRUS = 7 # Поверхности Вейерштрасса
|
|
35
|
+
SCHWARZ_P = 8 # Поверхности Шварца
|
|
36
|
+
NEOVIUS = 9 # Поверхности Неовиуса
|
|
37
|
+
LAVA_LAMPS = 10 # Лавовые лампы (метаболы)
|
|
38
|
+
FIBONACCI_SPIRAL = 11 # Спираль Фибоначчи в 3D
|
|
39
|
+
QUASI_CRYSTAL = 12 # Квазикристаллы
|
|
40
|
+
TESSELLATION = 13 # Тесселяции пространства
|
|
41
|
+
FRACTAL_SPONGE = 14 # Губка Менгера
|
|
42
|
+
KNOTS_LINKS = 15 # Узлы и зацепления
|
|
43
|
+
HELICOID = 16 # Геликоид
|
|
44
|
+
CATENOID = 17 # Катеноид
|
|
45
|
+
ENNEPER = 18 # Поверхность Эннепера
|
|
46
|
+
CORKSCREW = 19 # Штопор Архимеда
|
|
47
|
+
MOBIUS = 20 # Лента Мёбиуса
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class PatternParameters:
|
|
51
|
+
"""Параметры для генерации геометрических паттернов"""
|
|
52
|
+
# Общие параметры
|
|
53
|
+
scale: float = 1.0
|
|
54
|
+
resolution: float = 0.01
|
|
55
|
+
thickness: float = 0.1
|
|
56
|
+
symmetry: int = 4
|
|
57
|
+
noise_amplitude: float = 0.0
|
|
58
|
+
noise_scale: float = 0.1
|
|
59
|
+
|
|
60
|
+
# Специфичные параметры
|
|
61
|
+
crystal_type: str = "cubic" # cubic, hexagonal, tetragonal, etc.
|
|
62
|
+
cell_size: float = 0.5
|
|
63
|
+
wall_thickness: float = 0.05
|
|
64
|
+
sphere_radius: float = 0.3
|
|
65
|
+
packing_density: float = 0.6
|
|
66
|
+
fibonacci_ratio: float = 1.61803398875 # Золотое сечение
|
|
67
|
+
quasi_symmetry: int = 5 # Симметрия для квазикристаллов
|
|
68
|
+
|
|
69
|
+
# Параметры фракталов
|
|
70
|
+
fractal_iterations: int = 3
|
|
71
|
+
fractal_scale: float = 0.5
|
|
72
|
+
|
|
73
|
+
# Параметры поверхностей
|
|
74
|
+
surface_threshold: float = 0.0
|
|
75
|
+
surface_isolevel: float = 0.5
|
|
76
|
+
|
|
77
|
+
def __post_init__(self):
|
|
78
|
+
# Нормализация параметров
|
|
79
|
+
self.scale = max(0.001, self.scale)
|
|
80
|
+
self.resolution = max(0.001, self.resolution)
|
|
81
|
+
self.thickness = max(0.001, self.thickness)
|
|
82
|
+
|
|
83
|
+
# ----------------------------------------------------------------------
|
|
84
|
+
# Математические функции для 3D паттернов
|
|
85
|
+
# ----------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
class MathematicalSurfaces3D:
|
|
88
|
+
"""Математические поверхности в 3D"""
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
@jit(nopython=True, cache=True)
|
|
92
|
+
def gyroid(x: float, y: float, z: float, scale: float = 1.0) -> float:
|
|
93
|
+
"""
|
|
94
|
+
Гироидная поверхность - минимальная поверхность с тройной периодичностью
|
|
95
|
+
|
|
96
|
+
f(x,y,z) = sin(x)*cos(y) + sin(y)*cos(z) + sin(z)*cos(x)
|
|
97
|
+
"""
|
|
98
|
+
sx = np.sin(x * scale)
|
|
99
|
+
cx = np.cos(x * scale)
|
|
100
|
+
sy = np.sin(y * scale)
|
|
101
|
+
cy = np.cos(y * scale)
|
|
102
|
+
sz = np.sin(z * scale)
|
|
103
|
+
cz = np.cos(z * scale)
|
|
104
|
+
|
|
105
|
+
return sx * cy + sy * cz + sz * cx
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
@jit(nopython=True, cache=True)
|
|
109
|
+
def schwarz_p(x: float, y: float, z: float, scale: float = 1.0) -> float:
|
|
110
|
+
"""Поверхность Шварца P (примитивная)"""
|
|
111
|
+
sx = np.sin(x * scale)
|
|
112
|
+
cx = np.cos(x * scale)
|
|
113
|
+
sy = np.sin(y * scale)
|
|
114
|
+
cy = np.cos(y * scale)
|
|
115
|
+
sz = np.sin(z * scale)
|
|
116
|
+
cz = np.cos(z * scale)
|
|
117
|
+
|
|
118
|
+
return cx + cy + cz
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
@jit(nopython=True, cache=True)
|
|
122
|
+
def schwarz_d(x: float, y: float, z: float, scale: float = 1.0) -> float:
|
|
123
|
+
"""Поверхность Шварца D (алмазная)"""
|
|
124
|
+
sx = np.sin(x * scale)
|
|
125
|
+
cx = np.cos(x * scale)
|
|
126
|
+
sy = np.sin(y * scale)
|
|
127
|
+
cy = np.cos(y * scale)
|
|
128
|
+
sz = np.sin(z * scale)
|
|
129
|
+
cz = np.cos(z * scale)
|
|
130
|
+
|
|
131
|
+
return (sx * sy * sz +
|
|
132
|
+
sx * cy * cz +
|
|
133
|
+
cx * sy * cz +
|
|
134
|
+
cx * cy * sz)
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
@jit(nopython=True, cache=True)
|
|
138
|
+
def neovius(x: float, y: float, z: float, scale: float = 1.0) -> float:
|
|
139
|
+
"""Поверхность Неовиуса"""
|
|
140
|
+
sx = np.sin(x * scale)
|
|
141
|
+
cx = np.cos(x * scale)
|
|
142
|
+
sy = np.sin(y * scale)
|
|
143
|
+
cy = np.cos(y * scale)
|
|
144
|
+
sz = np.sin(z * scale)
|
|
145
|
+
cz = np.cos(z * scale)
|
|
146
|
+
|
|
147
|
+
return (3 * (cx + cy + cz) +
|
|
148
|
+
4 * cx * cy * cz)
|
|
149
|
+
|
|
150
|
+
@staticmethod
|
|
151
|
+
@jit(nopython=True, cache=True)
|
|
152
|
+
def weierstrass(x: float, y: float, z: float, scale: float = 1.0) -> float:
|
|
153
|
+
"""Поверхность Вейерштрасса"""
|
|
154
|
+
return (np.cos(x * scale) * np.cos(y * scale) * np.cos(z * scale) -
|
|
155
|
+
np.sin(x * scale) * np.sin(y * scale) * np.sin(z * scale))
|
|
156
|
+
|
|
157
|
+
@staticmethod
|
|
158
|
+
@jit(nopython=True, cache=True)
|
|
159
|
+
def helicoid(x: float, y: float, z: float, scale: float = 1.0) -> float:
|
|
160
|
+
"""Геликоид"""
|
|
161
|
+
return x * np.sin(z * scale) - y * np.cos(z * scale)
|
|
162
|
+
|
|
163
|
+
@staticmethod
|
|
164
|
+
@jit(nopython=True, cache=True)
|
|
165
|
+
def catenoid(x: float, y: float, z: float, scale: float = 1.0) -> float:
|
|
166
|
+
"""Катеноид"""
|
|
167
|
+
r = np.sqrt(x*x + y*y)
|
|
168
|
+
return np.cosh(r * scale) - z
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
@jit(nopython=True, cache=True)
|
|
172
|
+
def enneper(x: float, y: float, z: float, scale: float = 1.0) -> float:
|
|
173
|
+
"""Поверхность Эннепера"""
|
|
174
|
+
u = x * scale
|
|
175
|
+
v = y * scale
|
|
176
|
+
|
|
177
|
+
# Параметрическое представление
|
|
178
|
+
X = u - u**3/3 + u*v*v
|
|
179
|
+
Y = v - v**3/3 + v*u*u
|
|
180
|
+
Z = u*u - v*v
|
|
181
|
+
|
|
182
|
+
# Расстояние до поверхности
|
|
183
|
+
return np.sqrt((x - X)**2 + (y - Y)**2 + (z - Z)**2) - 0.1
|
|
184
|
+
|
|
185
|
+
@staticmethod
|
|
186
|
+
@jit(nopython=True, cache=True)
|
|
187
|
+
def mobius(x: float, y: float, z: float, scale: float = 1.0) -> float:
|
|
188
|
+
"""Лента Мёбиуса"""
|
|
189
|
+
u = np.arctan2(y, x)
|
|
190
|
+
r = np.sqrt(x*x + y*y)
|
|
191
|
+
|
|
192
|
+
# Параметрическое представление
|
|
193
|
+
R = 1.0
|
|
194
|
+
width = 0.3
|
|
195
|
+
|
|
196
|
+
X = (R + width * np.cos(u/2)) * np.cos(u)
|
|
197
|
+
Y = (R + width * np.cos(u/2)) * np.sin(u)
|
|
198
|
+
Z = width * np.sin(u/2)
|
|
199
|
+
|
|
200
|
+
return np.sqrt((x - X)**2 + (y - Y)**2 + (z - Z)**2) - 0.05
|
|
201
|
+
|
|
202
|
+
@staticmethod
|
|
203
|
+
@jit(nopython=True, cache=True)
|
|
204
|
+
def corkscrew(x: float, y: float, z: float, scale: float = 1.0) -> float:
|
|
205
|
+
"""Штопор Архимеда"""
|
|
206
|
+
r = np.sqrt(x*x + y*y)
|
|
207
|
+
theta = np.arctan2(y, x)
|
|
208
|
+
|
|
209
|
+
# Уравнение штопора: z = a * theta
|
|
210
|
+
a = 0.5
|
|
211
|
+
return z - a * theta * scale
|
|
212
|
+
|
|
213
|
+
@staticmethod
|
|
214
|
+
@jit(nopython=True, cache=True)
|
|
215
|
+
def klein_bottle(x: float, y: float, z: float, scale: float = 1.0) -> float:
|
|
216
|
+
"""Бутылка Клейна (упрощенная)"""
|
|
217
|
+
u = np.arctan2(y, x) * 2
|
|
218
|
+
v = z * 2
|
|
219
|
+
|
|
220
|
+
# Параметрическое представление
|
|
221
|
+
r = 4 * (1 - np.cos(u)/2)
|
|
222
|
+
|
|
223
|
+
X = 6 * np.cos(u) * (1 + np.sin(u)) + r * np.cos(u) * np.cos(v)
|
|
224
|
+
Y = 16 * np.sin(u) + r * np.sin(u) * np.cos(v)
|
|
225
|
+
Z = r * np.sin(v)
|
|
226
|
+
|
|
227
|
+
return np.sqrt((x - X/10)**2 + (y - Y/10)**2 + (z - Z/10)**2) - 0.1
|
|
228
|
+
|
|
229
|
+
# ----------------------------------------------------------------------
|
|
230
|
+
# Генераторы базовых геометрических паттернов
|
|
231
|
+
# ----------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
class GeometricPatternGenerator3D:
|
|
234
|
+
"""Генератор геометрических 3D паттернов"""
|
|
235
|
+
|
|
236
|
+
def __init__(self, seed: int = 42):
|
|
237
|
+
self.seed = seed
|
|
238
|
+
np.random.seed(seed)
|
|
239
|
+
self.math_surfaces = MathematicalSurfaces3D()
|
|
240
|
+
self.cache = {}
|
|
241
|
+
|
|
242
|
+
def generate_pattern(self,
|
|
243
|
+
pattern_type: GeometricPattern3D,
|
|
244
|
+
dimensions: Tuple[int, int, int],
|
|
245
|
+
params: Optional[PatternParameters] = None) -> np.ndarray:
|
|
246
|
+
"""
|
|
247
|
+
Генерация 3D паттерна
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
pattern_type: Тип паттерна
|
|
251
|
+
dimensions: Размеры объема (D, H, W)
|
|
252
|
+
params: Параметры генерации
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
3D текстура (D, H, W, 4) RGBA
|
|
256
|
+
"""
|
|
257
|
+
if params is None:
|
|
258
|
+
params = PatternParameters()
|
|
259
|
+
|
|
260
|
+
# Проверка кэша
|
|
261
|
+
cache_key = (pattern_type.value, dimensions, hash(frozenset(params.__dict__.items())))
|
|
262
|
+
if cache_key in self.cache:
|
|
263
|
+
return self.cache[cache_key].copy()
|
|
264
|
+
|
|
265
|
+
print(f"Generating {pattern_type.name} pattern {dimensions}...")
|
|
266
|
+
|
|
267
|
+
# Выбор метода генерации
|
|
268
|
+
generator_map = {
|
|
269
|
+
GeometricPattern3D.CRYSTAL_LATTICE: self._generate_crystal_lattice,
|
|
270
|
+
GeometricPattern3D.HONEYCOMB: self._generate_honeycomb,
|
|
271
|
+
GeometricPattern3D.GYROID: self._generate_gyroid,
|
|
272
|
+
GeometricPattern3D.DIAMOND_STRUCTURE: self._generate_diamond_structure,
|
|
273
|
+
GeometricPattern3D.SPHERE_PACKING: self._generate_sphere_packing,
|
|
274
|
+
GeometricPattern3D.VORONOI_CELLS: self._generate_voronoi_cells,
|
|
275
|
+
GeometricPattern3D.WEIRELEN_PIRUS: self._generate_weierstrass,
|
|
276
|
+
GeometricPattern3D.SCHWARZ_P: self._generate_schwarz_p,
|
|
277
|
+
GeometricPattern3D.NEOVIUS: self._generate_neovius,
|
|
278
|
+
GeometricPattern3D.LAVA_LAMPS: self._generate_lava_lamps,
|
|
279
|
+
GeometricPattern3D.FIBONACCI_SPIRAL: self._generate_fibonacci_spiral,
|
|
280
|
+
GeometricPattern3D.QUASI_CRYSTAL: self._generate_quasi_crystal,
|
|
281
|
+
GeometricPattern3D.TESSELLATION: self._generate_tessellation,
|
|
282
|
+
GeometricPattern3D.FRACTAL_SPONGE: self._generate_fractal_sponge,
|
|
283
|
+
GeometricPattern3D.KNOTS_LINKS: self._generate_knots,
|
|
284
|
+
GeometricPattern3D.HELICOID: self._generate_helicoid,
|
|
285
|
+
GeometricPattern3D.CATENOID: self._generate_catenoid,
|
|
286
|
+
GeometricPattern3D.ENNEPER: self._generate_enneper,
|
|
287
|
+
GeometricPattern3D.CORKSCREW: self._generate_corkscrew,
|
|
288
|
+
GeometricPattern3D.MOBIUS: self._generate_mobius,
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if pattern_type not in generator_map:
|
|
292
|
+
raise ValueError(f"Unknown pattern type: {pattern_type}")
|
|
293
|
+
|
|
294
|
+
# Генерация
|
|
295
|
+
texture = generator_map[pattern_type](dimensions, params)
|
|
296
|
+
|
|
297
|
+
# Кэширование
|
|
298
|
+
self.cache[cache_key] = texture.copy()
|
|
299
|
+
if len(self.cache) > 50:
|
|
300
|
+
self.cache.pop(next(iter(self.cache)))
|
|
301
|
+
|
|
302
|
+
return texture
|
|
303
|
+
|
|
304
|
+
def _generate_crystal_lattice(self,
|
|
305
|
+
dimensions: Tuple[int, int, int],
|
|
306
|
+
params: PatternParameters) -> np.ndarray:
|
|
307
|
+
"""Генерация кристаллической решетки"""
|
|
308
|
+
depth, height, width = dimensions
|
|
309
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
310
|
+
|
|
311
|
+
# Выбор типа решетки
|
|
312
|
+
if params.crystal_type == "cubic":
|
|
313
|
+
# Кубическая решетка
|
|
314
|
+
for i in range(depth):
|
|
315
|
+
for j in range(height):
|
|
316
|
+
for k in range(width):
|
|
317
|
+
x = i / depth * params.scale * 2 * np.pi
|
|
318
|
+
y = j / height * params.scale * 2 * np.pi
|
|
319
|
+
z = k / width * params.scale * 2 * np.pi
|
|
320
|
+
|
|
321
|
+
# Периодические синусоиды для каждой оси
|
|
322
|
+
value = (np.sin(x) + np.sin(y) + np.sin(z)) / 3
|
|
323
|
+
|
|
324
|
+
# Порог для создания поверхности
|
|
325
|
+
if abs(value) < params.thickness:
|
|
326
|
+
# Цвет в зависимости от ориентации
|
|
327
|
+
r = (np.sin(x) + 1) / 2
|
|
328
|
+
g = (np.sin(y) + 1) / 2
|
|
329
|
+
b = (np.sin(z) + 1) / 2
|
|
330
|
+
|
|
331
|
+
# Прозрачность зависит от расстояния до поверхности
|
|
332
|
+
alpha = 1.0 - abs(value) / params.thickness
|
|
333
|
+
|
|
334
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
335
|
+
|
|
336
|
+
elif params.crystal_type == "hexagonal":
|
|
337
|
+
# Гексагональная решетка
|
|
338
|
+
for i in range(depth):
|
|
339
|
+
for j in range(height):
|
|
340
|
+
for k in range(width):
|
|
341
|
+
x = i / depth * params.scale * 4 * np.pi
|
|
342
|
+
y = j / height * params.scale * 4 * np.pi
|
|
343
|
+
z = k / width * params.scale * 2 * np.pi
|
|
344
|
+
|
|
345
|
+
# Гексагональная симметрия
|
|
346
|
+
value = (np.sin(x) +
|
|
347
|
+
np.sin(x/2 + y*np.sqrt(3)/2) +
|
|
348
|
+
np.sin(-x/2 + y*np.sqrt(3)/2) +
|
|
349
|
+
np.sin(z)) / 4
|
|
350
|
+
|
|
351
|
+
if abs(value) < params.thickness:
|
|
352
|
+
r = (np.sin(x) + 1) / 2
|
|
353
|
+
g = (np.sin(y) + 1) / 2
|
|
354
|
+
b = (np.sin(z) + 1) / 2
|
|
355
|
+
alpha = 1.0 - abs(value) / params.thickness
|
|
356
|
+
|
|
357
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
358
|
+
|
|
359
|
+
elif params.crystal_type == "diamond":
|
|
360
|
+
# Алмазная структура
|
|
361
|
+
for i in range(depth):
|
|
362
|
+
for j in range(height):
|
|
363
|
+
for k in range(width):
|
|
364
|
+
x = i / depth * params.scale * 2 * np.pi
|
|
365
|
+
y = j / height * params.scale * 2 * np.pi
|
|
366
|
+
z = k / width * params.scale * 2 * np.pi
|
|
367
|
+
|
|
368
|
+
# Более сложная структура
|
|
369
|
+
value = (np.sin(x) * np.sin(y) * np.sin(z) +
|
|
370
|
+
np.sin(x) * np.cos(y) * np.cos(z) +
|
|
371
|
+
np.cos(x) * np.sin(y) * np.cos(z) +
|
|
372
|
+
np.cos(x) * np.cos(y) * np.sin(z)) / 4
|
|
373
|
+
|
|
374
|
+
if abs(value) < params.thickness:
|
|
375
|
+
# Переливчатый цвет как у алмаза
|
|
376
|
+
hue = (x + y + z) % (2 * np.pi)
|
|
377
|
+
r = (np.sin(hue) + 1) / 2
|
|
378
|
+
g = (np.sin(hue + 2*np.pi/3) + 1) / 2
|
|
379
|
+
b = (np.sin(hue + 4*np.pi/3) + 1) / 2
|
|
380
|
+
alpha = 1.0 - abs(value) / params.thickness
|
|
381
|
+
|
|
382
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
383
|
+
|
|
384
|
+
# Добавление шума если нужно
|
|
385
|
+
if params.noise_amplitude > 0:
|
|
386
|
+
texture = self._add_noise(texture, params.noise_amplitude, params.noise_scale)
|
|
387
|
+
|
|
388
|
+
return texture
|
|
389
|
+
|
|
390
|
+
def _generate_honeycomb(self,
|
|
391
|
+
dimensions: Tuple[int, int, int],
|
|
392
|
+
params: PatternParameters) -> np.ndarray:
|
|
393
|
+
"""Генерация сотовой структуры (гексагональные ячейки)"""
|
|
394
|
+
depth, height, width = dimensions
|
|
395
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
396
|
+
|
|
397
|
+
# Параметры сот
|
|
398
|
+
cell_size = params.cell_size
|
|
399
|
+
wall_thickness = params.wall_thickness
|
|
400
|
+
|
|
401
|
+
# Гексагональная сетка
|
|
402
|
+
hex_radius = cell_size
|
|
403
|
+
hex_height = np.sqrt(3) * hex_radius
|
|
404
|
+
|
|
405
|
+
for i in range(depth):
|
|
406
|
+
z = i / depth * params.scale
|
|
407
|
+
|
|
408
|
+
for j in range(height):
|
|
409
|
+
y = j / height * params.scale
|
|
410
|
+
|
|
411
|
+
for k in range(width):
|
|
412
|
+
x = k / width * params.scale
|
|
413
|
+
|
|
414
|
+
# Преобразование в гексагональные координаты
|
|
415
|
+
# Сдвиг четных строк
|
|
416
|
+
q = x / (hex_radius * 1.5)
|
|
417
|
+
r = (-x / (hex_radius * 1.5) + y / (hex_height)) / 2
|
|
418
|
+
|
|
419
|
+
# Округление до ближайшего центра гексагона
|
|
420
|
+
q_round = round(q)
|
|
421
|
+
r_round = round(r)
|
|
422
|
+
|
|
423
|
+
# Обратное преобразование
|
|
424
|
+
x_center = q_round * hex_radius * 1.5
|
|
425
|
+
y_center = (2 * r_round + q_round) * hex_height / 2
|
|
426
|
+
|
|
427
|
+
# Расстояние до центра гексагона
|
|
428
|
+
dx = x - x_center
|
|
429
|
+
dy = y - y_center
|
|
430
|
+
dist = np.sqrt(dx*dx + dy*dy)
|
|
431
|
+
|
|
432
|
+
# Толщина стенок в зависимости от Z
|
|
433
|
+
z_factor = 0.8 + 0.2 * np.sin(z * 2 * np.pi)
|
|
434
|
+
effective_thickness = wall_thickness * z_factor
|
|
435
|
+
|
|
436
|
+
# Если внутри гексагона, но не слишком близко к центру
|
|
437
|
+
if dist < hex_radius and dist > hex_radius - effective_thickness:
|
|
438
|
+
# Цвет сот (золотистый)
|
|
439
|
+
r_color = 0.9 # Желтый компонент
|
|
440
|
+
g_color = 0.7 # Золотистый
|
|
441
|
+
b_color = 0.1 # Темно-желтый
|
|
442
|
+
|
|
443
|
+
# Интенсивность зависит от расстояния до стенки
|
|
444
|
+
intensity = 1.0 - abs(dist - (hex_radius - effective_thickness/2)) / (effective_thickness/2)
|
|
445
|
+
alpha = intensity * 0.8
|
|
446
|
+
|
|
447
|
+
texture[i, j, k] = [r_color, g_color, b_color, alpha]
|
|
448
|
+
|
|
449
|
+
# Также создаем "дно" сот (нижняя часть)
|
|
450
|
+
z_thickness = 0.05
|
|
451
|
+
if z < z_thickness and dist < hex_radius - effective_thickness:
|
|
452
|
+
# Более темное дно
|
|
453
|
+
texture[i, j, k] = [0.6, 0.5, 0.1, 0.9]
|
|
454
|
+
|
|
455
|
+
return texture
|
|
456
|
+
|
|
457
|
+
def _generate_gyroid(self,
|
|
458
|
+
dimensions: Tuple[int, int, int],
|
|
459
|
+
params: PatternParameters) -> np.ndarray:
|
|
460
|
+
"""Генерация гироидной поверхности"""
|
|
461
|
+
depth, height, width = dimensions
|
|
462
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
463
|
+
|
|
464
|
+
for i in range(depth):
|
|
465
|
+
for j in range(height):
|
|
466
|
+
for k in range(width):
|
|
467
|
+
x = i / depth * params.scale * 2 * np.pi
|
|
468
|
+
y = j / height * params.scale * 2 * np.pi
|
|
469
|
+
z = k / width * params.scale * 2 * np.pi
|
|
470
|
+
|
|
471
|
+
# Значение гироидной функции
|
|
472
|
+
value = self.math_surfaces.gyroid(x, y, z)
|
|
473
|
+
|
|
474
|
+
# Пороговое значение для поверхности
|
|
475
|
+
threshold = params.surface_threshold
|
|
476
|
+
|
|
477
|
+
if abs(value - threshold) < params.thickness:
|
|
478
|
+
# Цвет в зависимости от нормали
|
|
479
|
+
# Приближенная нормаль через градиент
|
|
480
|
+
eps = 0.01
|
|
481
|
+
dx = (self.math_surfaces.gyroid(x+eps, y, z) -
|
|
482
|
+
self.math_surfaces.gyroid(x-eps, y, z)) / (2*eps)
|
|
483
|
+
dy = (self.math_surfaces.gyroid(x, y+eps, z) -
|
|
484
|
+
self.math_surfaces.gyroid(x, y-eps, z)) / (2*eps)
|
|
485
|
+
dz = (self.math_surfaces.gyroid(x, y, z+eps) -
|
|
486
|
+
self.math_surfaces.gyroid(x, y, z-eps)) / (2*eps)
|
|
487
|
+
|
|
488
|
+
# Нормализация градиента
|
|
489
|
+
norm = np.sqrt(dx*dx + dy*dy + dz*dz) + 1e-8
|
|
490
|
+
nx = dx / norm
|
|
491
|
+
ny = dy / norm
|
|
492
|
+
nz = dz / norm
|
|
493
|
+
|
|
494
|
+
# Цвет на основе нормали
|
|
495
|
+
r = (nx + 1) / 2
|
|
496
|
+
g = (ny + 1) / 2
|
|
497
|
+
b = (nz + 1) / 2
|
|
498
|
+
|
|
499
|
+
# Прозрачность
|
|
500
|
+
alpha = 1.0 - abs(value - threshold) / params.thickness
|
|
501
|
+
|
|
502
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
503
|
+
|
|
504
|
+
return texture
|
|
505
|
+
|
|
506
|
+
def _generate_diamond_structure(self,
|
|
507
|
+
dimensions: Tuple[int, int, int],
|
|
508
|
+
params: PatternParameters) -> np.ndarray:
|
|
509
|
+
"""Генерация алмазной структуры (как в кристаллах алмаза)"""
|
|
510
|
+
depth, height, width = dimensions
|
|
511
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
512
|
+
|
|
513
|
+
# Алмазная кубическая структура
|
|
514
|
+
# Атомы углерода в позициях (0,0,0) и (1/4,1/4,1/4) и их трансляции
|
|
515
|
+
|
|
516
|
+
lattice_constant = params.cell_size
|
|
517
|
+
|
|
518
|
+
# Позиции атомов в элементарной ячейке
|
|
519
|
+
positions = [
|
|
520
|
+
(0.0, 0.0, 0.0),
|
|
521
|
+
(0.0, 0.5, 0.5),
|
|
522
|
+
(0.5, 0.0, 0.5),
|
|
523
|
+
(0.5, 0.5, 0.0),
|
|
524
|
+
(0.25, 0.25, 0.25),
|
|
525
|
+
(0.25, 0.75, 0.75),
|
|
526
|
+
(0.75, 0.25, 0.75),
|
|
527
|
+
(0.75, 0.75, 0.25)
|
|
528
|
+
]
|
|
529
|
+
|
|
530
|
+
# Радиус атома (условный)
|
|
531
|
+
atom_radius = 0.1 * lattice_constant
|
|
532
|
+
|
|
533
|
+
for i in range(depth):
|
|
534
|
+
z = i / depth * params.scale
|
|
535
|
+
|
|
536
|
+
for j in range(height):
|
|
537
|
+
y = j / height * params.scale
|
|
538
|
+
|
|
539
|
+
for k in range(width):
|
|
540
|
+
x = k / width * params.scale
|
|
541
|
+
|
|
542
|
+
# Проверяем расстояние до каждого атома в ближайших ячейках
|
|
543
|
+
min_dist = float('inf')
|
|
544
|
+
|
|
545
|
+
# Проверяем ближайшие элементарные ячейки
|
|
546
|
+
for dx in [-1, 0, 1]:
|
|
547
|
+
for dy in [-1, 0, 1]:
|
|
548
|
+
for dz in [-1, 0, 1]:
|
|
549
|
+
# Смещение ячейки
|
|
550
|
+
cell_x = dx * lattice_constant
|
|
551
|
+
cell_y = dy * lattice_constant
|
|
552
|
+
cell_z = dz * lattice_constant
|
|
553
|
+
|
|
554
|
+
# Для каждой позиции атома в ячейке
|
|
555
|
+
for pos in positions:
|
|
556
|
+
atom_x = cell_x + pos[0] * lattice_constant
|
|
557
|
+
atom_y = cell_y + pos[1] * lattice_constant
|
|
558
|
+
atom_z = cell_z + pos[2] * lattice_constant
|
|
559
|
+
|
|
560
|
+
# Расстояние до атома
|
|
561
|
+
dist = np.sqrt((x - atom_x)**2 +
|
|
562
|
+
(y - atom_y)**2 +
|
|
563
|
+
(z - atom_z)**2)
|
|
564
|
+
|
|
565
|
+
min_dist = min(min_dist, dist)
|
|
566
|
+
|
|
567
|
+
# Если близко к атому
|
|
568
|
+
if min_dist < atom_radius:
|
|
569
|
+
# Переливчатый цвет как у алмаза
|
|
570
|
+
# Зависит от ориентации
|
|
571
|
+
hue = (x + y + z) * 10 % 1.0
|
|
572
|
+
|
|
573
|
+
# Преобразование HSV в RGB
|
|
574
|
+
from colorsys import hsv_to_rgb
|
|
575
|
+
r, g, b = hsv_to_rgb(hue, 0.3, 1.0)
|
|
576
|
+
|
|
577
|
+
# Интенсивность зависит от расстояния
|
|
578
|
+
intensity = 1.0 - min_dist / atom_radius
|
|
579
|
+
alpha = intensity * 0.9
|
|
580
|
+
|
|
581
|
+
# Добавляем внутреннее свечение
|
|
582
|
+
glow = np.exp(-min_dist * 20) * 0.3
|
|
583
|
+
r += glow
|
|
584
|
+
g += glow
|
|
585
|
+
b += glow
|
|
586
|
+
|
|
587
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
588
|
+
|
|
589
|
+
# Также показываем связи между атомами
|
|
590
|
+
# (пространство между атомами в тетраэдре)
|
|
591
|
+
elif min_dist < atom_radius * 2:
|
|
592
|
+
# Слабое свечение связей
|
|
593
|
+
intensity = np.exp(-min_dist * 10) * 0.2
|
|
594
|
+
texture[i, j, k, 3] = intensity
|
|
595
|
+
|
|
596
|
+
return texture
|
|
597
|
+
|
|
598
|
+
def _generate_sphere_packing(self,
|
|
599
|
+
dimensions: Tuple[int, int, int],
|
|
600
|
+
params: PatternParameters) -> np.ndarray:
|
|
601
|
+
"""Генерация упаковки сфер (плотнейшая упаковка)"""
|
|
602
|
+
depth, height, width = dimensions
|
|
603
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
604
|
+
|
|
605
|
+
# Параметры
|
|
606
|
+
sphere_radius = params.sphere_radius
|
|
607
|
+
packing_type = "fcc" # ГЦК (гранецентрированная кубическая)
|
|
608
|
+
|
|
609
|
+
if packing_type == "fcc":
|
|
610
|
+
# ГЦК упаковка
|
|
611
|
+
# Базовые векторы
|
|
612
|
+
a1 = np.array([1.0, 0.0, 0.0])
|
|
613
|
+
a2 = np.array([0.5, np.sqrt(3)/2, 0.0])
|
|
614
|
+
a3 = np.array([0.5, np.sqrt(3)/6, np.sqrt(2/3)])
|
|
615
|
+
|
|
616
|
+
# Позиции в элементарной ячейке
|
|
617
|
+
positions = [
|
|
618
|
+
np.array([0.0, 0.0, 0.0]),
|
|
619
|
+
np.array([0.5, 0.5, 0.0]),
|
|
620
|
+
np.array([0.5, 0.0, 0.5]),
|
|
621
|
+
np.array([0.0, 0.5, 0.5])
|
|
622
|
+
]
|
|
623
|
+
|
|
624
|
+
# Масштабирование
|
|
625
|
+
scale_factor = params.scale * 2
|
|
626
|
+
|
|
627
|
+
for i in range(depth):
|
|
628
|
+
z = i / depth * scale_factor
|
|
629
|
+
|
|
630
|
+
for j in range(height):
|
|
631
|
+
y = j / height * scale_factor
|
|
632
|
+
|
|
633
|
+
for k in range(width):
|
|
634
|
+
x = k / width * scale_factor
|
|
635
|
+
|
|
636
|
+
pos = np.array([x, y, z])
|
|
637
|
+
min_dist = float('inf')
|
|
638
|
+
|
|
639
|
+
# Проверяем ближайшие ячейки
|
|
640
|
+
for nx in range(-1, 2):
|
|
641
|
+
for ny in range(-1, 2):
|
|
642
|
+
for nz in range(-1, 2):
|
|
643
|
+
# Смещение ячейки
|
|
644
|
+
cell_offset = nx * a1 + ny * a2 + nz * a3
|
|
645
|
+
|
|
646
|
+
# Для каждой позиции в ячейке
|
|
647
|
+
for base_pos in positions:
|
|
648
|
+
sphere_center = cell_offset + base_pos
|
|
649
|
+
|
|
650
|
+
dist = np.linalg.norm(pos - sphere_center)
|
|
651
|
+
min_dist = min(min_dist, dist)
|
|
652
|
+
|
|
653
|
+
# Если внутри сферы
|
|
654
|
+
if min_dist < sphere_radius:
|
|
655
|
+
# Цвет сферы
|
|
656
|
+
# Зависит от положения для визуализации структуры
|
|
657
|
+
hue = (x + z) % 1.0
|
|
658
|
+
from colorsys import hsv_to_rgb
|
|
659
|
+
r, g, b = hsv_to_rgb(hue, 0.7, 1.0)
|
|
660
|
+
|
|
661
|
+
# Интенсивность
|
|
662
|
+
intensity = 1.0 - min_dist / sphere_radius
|
|
663
|
+
alpha = intensity * 0.9
|
|
664
|
+
|
|
665
|
+
# Градиент от центра к краю
|
|
666
|
+
gradient = min_dist / sphere_radius
|
|
667
|
+
r *= (1 - gradient * 0.3)
|
|
668
|
+
g *= (1 - gradient * 0.3)
|
|
669
|
+
b *= (1 - gradient * 0.3)
|
|
670
|
+
|
|
671
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
672
|
+
|
|
673
|
+
# Контакты между сферами
|
|
674
|
+
elif min_dist < sphere_radius * 1.1:
|
|
675
|
+
# Слабое свечение в местах контакта
|
|
676
|
+
intensity = np.exp(-(min_dist - sphere_radius) * 20) * 0.3
|
|
677
|
+
texture[i, j, k, 3] = intensity
|
|
678
|
+
|
|
679
|
+
return texture
|
|
680
|
+
|
|
681
|
+
def _generate_voronoi_cells(self,
|
|
682
|
+
dimensions: Tuple[int, int, int],
|
|
683
|
+
params: PatternParameters) -> np.ndarray:
|
|
684
|
+
"""Генерация ячеек Вороного в 3D"""
|
|
685
|
+
depth, height, width = dimensions
|
|
686
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
687
|
+
|
|
688
|
+
# Количество точек (центров ячеек)
|
|
689
|
+
num_points = int(params.packing_density * depth * height * width / 100)
|
|
690
|
+
|
|
691
|
+
# Генерация случайных точек
|
|
692
|
+
np.random.seed(self.seed)
|
|
693
|
+
points = np.random.rand(num_points, 3)
|
|
694
|
+
points[:, 0] *= depth
|
|
695
|
+
points[:, 1] *= height
|
|
696
|
+
points[:, 2] *= width
|
|
697
|
+
|
|
698
|
+
# Цвета для каждой точки
|
|
699
|
+
colors = np.random.rand(num_points, 3)
|
|
700
|
+
|
|
701
|
+
# Для каждого воксела находим ближайшую точку
|
|
702
|
+
for i in range(depth):
|
|
703
|
+
for j in range(height):
|
|
704
|
+
for k in range(width):
|
|
705
|
+
# Позиция воксела
|
|
706
|
+
pos = np.array([i, j, k])
|
|
707
|
+
|
|
708
|
+
# Находим ближайшую точку
|
|
709
|
+
distances = np.linalg.norm(points - pos, axis=1)
|
|
710
|
+
closest_idx = np.argmin(distances)
|
|
711
|
+
min_dist = distances[closest_idx]
|
|
712
|
+
|
|
713
|
+
# Также находим вторую ближайшую
|
|
714
|
+
distances[closest_idx] = float('inf')
|
|
715
|
+
second_closest_idx = np.argmin(distances)
|
|
716
|
+
second_dist = distances[second_closest_idx]
|
|
717
|
+
|
|
718
|
+
# Граница ячейки - середина между двумя ближайшими точками
|
|
719
|
+
boundary_dist = (min_dist + second_dist) / 2
|
|
720
|
+
|
|
721
|
+
# Если близко к границе
|
|
722
|
+
if abs(min_dist - boundary_dist) < params.wall_thickness * 5:
|
|
723
|
+
# Стенка ячейки
|
|
724
|
+
wall_intensity = 1.0 - abs(min_dist - boundary_dist) / (params.wall_thickness * 5)
|
|
725
|
+
|
|
726
|
+
# Цвет стенки (темный)
|
|
727
|
+
texture[i, j, k] = [0.2, 0.2, 0.2, wall_intensity * 0.8]
|
|
728
|
+
|
|
729
|
+
else:
|
|
730
|
+
# Внутри ячейки
|
|
731
|
+
color = colors[closest_idx]
|
|
732
|
+
|
|
733
|
+
# Интенсивность уменьшается к границе
|
|
734
|
+
cell_radius = boundary_dist
|
|
735
|
+
if cell_radius > 0:
|
|
736
|
+
intensity = 1.0 - min_dist / cell_radius
|
|
737
|
+
else:
|
|
738
|
+
intensity = 1.0
|
|
739
|
+
|
|
740
|
+
# Добавляем градиент
|
|
741
|
+
gradient = np.sin(i * 0.1) * np.cos(j * 0.1) * np.sin(k * 0.1) * 0.2 + 0.8
|
|
742
|
+
|
|
743
|
+
texture[i, j, k] = [
|
|
744
|
+
color[0] * gradient,
|
|
745
|
+
color[1] * gradient,
|
|
746
|
+
color[2] * gradient,
|
|
747
|
+
intensity * 0.6
|
|
748
|
+
]
|
|
749
|
+
|
|
750
|
+
return texture
|
|
751
|
+
|
|
752
|
+
def _generate_weierstrass(self,
|
|
753
|
+
dimensions: Tuple[int, int, int],
|
|
754
|
+
params: PatternParameters) -> np.ndarray:
|
|
755
|
+
"""Генерация поверхности Вейерштрасса"""
|
|
756
|
+
depth, height, width = dimensions
|
|
757
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
758
|
+
|
|
759
|
+
for i in range(depth):
|
|
760
|
+
for j in range(height):
|
|
761
|
+
for k in range(width):
|
|
762
|
+
x = i / depth * params.scale * 2 * np.pi
|
|
763
|
+
y = j / height * params.scale * 2 * np.pi
|
|
764
|
+
z = k / width * params.scale * 2 * np.pi
|
|
765
|
+
|
|
766
|
+
value = self.math_surfaces.weierstrass(x, y, z)
|
|
767
|
+
|
|
768
|
+
if abs(value) < params.thickness:
|
|
769
|
+
# Цвет на основе производных
|
|
770
|
+
eps = 0.01
|
|
771
|
+
dx = (self.math_surfaces.weierstrass(x+eps, y, z) -
|
|
772
|
+
self.math_surfaces.weierstrass(x-eps, y, z)) / (2*eps)
|
|
773
|
+
dy = (self.math_surfaces.weierstrass(x, y+eps, z) -
|
|
774
|
+
self.math_surfaces.weierstrass(x, y-eps, z)) / (2*eps)
|
|
775
|
+
dz = (self.math_surfaces.weierstrass(x, y, z+eps) -
|
|
776
|
+
self.math_surfaces.weierstrass(x, y, z-eps)) / (2*eps)
|
|
777
|
+
|
|
778
|
+
# Цвет как функция производных
|
|
779
|
+
r = (np.sin(dx * 10) + 1) / 2
|
|
780
|
+
g = (np.sin(dy * 10) + 1) / 2
|
|
781
|
+
b = (np.sin(dz * 10) + 1) / 2
|
|
782
|
+
|
|
783
|
+
alpha = 1.0 - abs(value) / params.thickness
|
|
784
|
+
|
|
785
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
786
|
+
|
|
787
|
+
return texture
|
|
788
|
+
|
|
789
|
+
def _generate_schwarz_p(self,
|
|
790
|
+
dimensions: Tuple[int, int, int],
|
|
791
|
+
params: PatternParameters) -> np.ndarray:
|
|
792
|
+
"""Генерация поверхности Шварца P"""
|
|
793
|
+
depth, height, width = dimensions
|
|
794
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
795
|
+
|
|
796
|
+
for i in range(depth):
|
|
797
|
+
for j in range(height):
|
|
798
|
+
for k in range(width):
|
|
799
|
+
x = i / depth * params.scale * 2 * np.pi
|
|
800
|
+
y = j / height * params.scale * 2 * np.pi
|
|
801
|
+
z = k / width * params.scale * 2 * np.pi
|
|
802
|
+
|
|
803
|
+
value = self.math_surfaces.schwarz_p(x, y, z)
|
|
804
|
+
|
|
805
|
+
if abs(value) < params.thickness:
|
|
806
|
+
# Цвет на основе положения
|
|
807
|
+
r = (np.sin(x) + 1) / 2
|
|
808
|
+
g = (np.sin(y) + 1) / 2
|
|
809
|
+
b = (np.sin(z) + 1) / 2
|
|
810
|
+
|
|
811
|
+
alpha = 1.0 - abs(value) / params.thickness
|
|
812
|
+
|
|
813
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
814
|
+
|
|
815
|
+
return texture
|
|
816
|
+
|
|
817
|
+
def _generate_neovius(self,
|
|
818
|
+
dimensions: Tuple[int, int, int],
|
|
819
|
+
params: PatternParameters) -> np.ndarray:
|
|
820
|
+
"""Генерация поверхности Неовиуса"""
|
|
821
|
+
depth, height, width = dimensions
|
|
822
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
823
|
+
|
|
824
|
+
for i in range(depth):
|
|
825
|
+
for j in range(height):
|
|
826
|
+
for k in range(width):
|
|
827
|
+
x = i / depth * params.scale * 2 * np.pi
|
|
828
|
+
y = j / height * params.scale * 2 * np.pi
|
|
829
|
+
z = k / width * params.scale * 2 * np.pi
|
|
830
|
+
|
|
831
|
+
value = self.math_surfaces.neovius(x, y, z)
|
|
832
|
+
|
|
833
|
+
if abs(value) < params.thickness:
|
|
834
|
+
# Сложный цветовой паттерн
|
|
835
|
+
r = np.sin(x * y) * 0.5 + 0.5
|
|
836
|
+
g = np.sin(y * z) * 0.5 + 0.5
|
|
837
|
+
b = np.sin(z * x) * 0.5 + 0.5
|
|
838
|
+
|
|
839
|
+
alpha = 1.0 - abs(value) / params.thickness
|
|
840
|
+
|
|
841
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
842
|
+
|
|
843
|
+
return texture
|
|
844
|
+
|
|
845
|
+
def _generate_lava_lamps(self,
|
|
846
|
+
dimensions: Tuple[int, int, int],
|
|
847
|
+
params: PatternParameters) -> np.ndarray:
|
|
848
|
+
"""Генерация метаболов (лавовые лампы)"""
|
|
849
|
+
depth, height, width = dimensions
|
|
850
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
851
|
+
|
|
852
|
+
# Несколько метаболов
|
|
853
|
+
num_metaballs = 8
|
|
854
|
+
centers = np.random.rand(num_metaballs, 3)
|
|
855
|
+
centers[:, 0] *= depth
|
|
856
|
+
centers[:, 1] *= height
|
|
857
|
+
centers[:, 2] *= width
|
|
858
|
+
|
|
859
|
+
# Размеры и силы
|
|
860
|
+
radii = np.random.rand(num_metaballs) * 10 + 5
|
|
861
|
+
strengths = np.random.rand(num_metaballs) * 2 + 1
|
|
862
|
+
|
|
863
|
+
# Цвета
|
|
864
|
+
colors = np.random.rand(num_metaballs, 3)
|
|
865
|
+
|
|
866
|
+
for i in range(depth):
|
|
867
|
+
for j in range(height):
|
|
868
|
+
for k in range(width):
|
|
869
|
+
pos = np.array([i, j, k])
|
|
870
|
+
|
|
871
|
+
# Сумма влияний всех метаболов
|
|
872
|
+
total_influence = 0.0
|
|
873
|
+
color_influence = np.zeros(3)
|
|
874
|
+
|
|
875
|
+
for m in range(num_metaballs):
|
|
876
|
+
dist = np.linalg.norm(pos - centers[m])
|
|
877
|
+
|
|
878
|
+
if dist < radii[m]:
|
|
879
|
+
# Влияние метабола (функция сглаживания)
|
|
880
|
+
influence = strengths[m] * (1 - dist / radii[m])**2
|
|
881
|
+
total_influence += influence
|
|
882
|
+
|
|
883
|
+
# Вклад в цвет
|
|
884
|
+
color_influence += colors[m] * influence
|
|
885
|
+
|
|
886
|
+
# Если суммарное влияние выше порога
|
|
887
|
+
if total_influence > params.surface_isolevel:
|
|
888
|
+
# Нормализованный цвет
|
|
889
|
+
if total_influence > 0:
|
|
890
|
+
avg_color = color_influence / total_influence
|
|
891
|
+
else:
|
|
892
|
+
avg_color = np.array([1.0, 1.0, 1.0])
|
|
893
|
+
|
|
894
|
+
# Интенсивность
|
|
895
|
+
intensity = min(1.0, (total_influence - params.surface_isolevel) / 0.5)
|
|
896
|
+
|
|
897
|
+
# Градиент от центра к краю
|
|
898
|
+
center_dist = min([np.linalg.norm(pos - centers[m]) / radii[m]
|
|
899
|
+
for m in range(num_metaballs)])
|
|
900
|
+
|
|
901
|
+
gradient = 1.0 - center_dist * 0.5
|
|
902
|
+
|
|
903
|
+
texture[i, j, k] = [
|
|
904
|
+
avg_color[0] * gradient,
|
|
905
|
+
avg_color[1] * gradient,
|
|
906
|
+
avg_color[2] * gradient,
|
|
907
|
+
intensity * 0.9
|
|
908
|
+
]
|
|
909
|
+
|
|
910
|
+
return texture
|
|
911
|
+
|
|
912
|
+
def _generate_fibonacci_spiral(self,
|
|
913
|
+
dimensions: Tuple[int, int, int],
|
|
914
|
+
params: PatternParameters) -> np.ndarray:
|
|
915
|
+
"""Генерация спирали Фибоначчи в 3D"""
|
|
916
|
+
depth, height, width = dimensions
|
|
917
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
918
|
+
|
|
919
|
+
# Количество точек
|
|
920
|
+
n_points = 200
|
|
921
|
+
phi = params.fibonacci_ratio # Золотое сечение
|
|
922
|
+
|
|
923
|
+
for i in range(depth):
|
|
924
|
+
z = (i / depth - 0.5) * 2 # От -1 до 1
|
|
925
|
+
|
|
926
|
+
for j in range(height):
|
|
927
|
+
y = (j / height - 0.5) * 2
|
|
928
|
+
|
|
929
|
+
for k in range(width):
|
|
930
|
+
x = (k / width - 0.5) * 2
|
|
931
|
+
|
|
932
|
+
pos = np.array([x, y, z])
|
|
933
|
+
min_dist = float('inf')
|
|
934
|
+
|
|
935
|
+
# Генерация точек Фибоначчи на сфере
|
|
936
|
+
for n in range(n_points):
|
|
937
|
+
# Угол в сферических координатах
|
|
938
|
+
theta = 2 * np.pi * n / phi
|
|
939
|
+
phi_angle = np.arccos(1 - 2 * (n + 0.5) / n_points)
|
|
940
|
+
|
|
941
|
+
# Декартовы координаты точки на сфере
|
|
942
|
+
point = np.array([
|
|
943
|
+
np.sin(phi_angle) * np.cos(theta),
|
|
944
|
+
np.sin(phi_angle) * np.sin(theta),
|
|
945
|
+
np.cos(phi_angle)
|
|
946
|
+
])
|
|
947
|
+
|
|
948
|
+
# Радиус спирали увеличивается с Z
|
|
949
|
+
radius = 0.3 + abs(z) * 0.2
|
|
950
|
+
point *= radius
|
|
951
|
+
|
|
952
|
+
# Смещение по Z для создания спирали
|
|
953
|
+
point[2] = z * 0.5 + np.sin(theta * 2) * 0.1
|
|
954
|
+
|
|
955
|
+
# Расстояние до точки
|
|
956
|
+
dist = np.linalg.norm(pos - point)
|
|
957
|
+
min_dist = min(min_dist, dist)
|
|
958
|
+
|
|
959
|
+
# Если близко к спирали
|
|
960
|
+
if min_dist < params.thickness:
|
|
961
|
+
# Цвет зависит от угла
|
|
962
|
+
angle = np.arctan2(y, x)
|
|
963
|
+
hue = angle / (2 * np.pi)
|
|
964
|
+
|
|
965
|
+
from colorsys import hsv_to_rgb
|
|
966
|
+
r, g, b = hsv_to_rgb(hue, 0.8, 1.0)
|
|
967
|
+
|
|
968
|
+
# Интенсивность
|
|
969
|
+
intensity = 1.0 - min_dist / params.thickness
|
|
970
|
+
alpha = intensity * 0.8
|
|
971
|
+
|
|
972
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
973
|
+
|
|
974
|
+
# Также создаем трубку вокруг спирали
|
|
975
|
+
elif min_dist < params.thickness * 2:
|
|
976
|
+
# Слабое свечение
|
|
977
|
+
intensity = np.exp(-min_dist * 10) * 0.3
|
|
978
|
+
texture[i, j, k, 3] = intensity
|
|
979
|
+
|
|
980
|
+
return texture
|
|
981
|
+
|
|
982
|
+
def _generate_quasi_crystal(self,
|
|
983
|
+
dimensions: Tuple[int, int, int],
|
|
984
|
+
params: PatternParameters) -> np.ndarray:
|
|
985
|
+
"""Генерация квазикристаллической структуры"""
|
|
986
|
+
depth, height, width = dimensions
|
|
987
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
988
|
+
|
|
989
|
+
# Для квазикристалла с 5-кратной симметрией
|
|
990
|
+
symmetry = params.quasi_symmetry
|
|
991
|
+
|
|
992
|
+
# Проекция из высшего измерения
|
|
993
|
+
n_dim = 5 # Размерность пространства для проекции
|
|
994
|
+
|
|
995
|
+
for i in range(depth):
|
|
996
|
+
for j in range(height):
|
|
997
|
+
for k in range(width):
|
|
998
|
+
x = i / depth * params.scale * 2 * np.pi
|
|
999
|
+
y = j / height * params.scale * 2 * np.pi
|
|
1000
|
+
z = k / width * params.scale * 2 * np.pi
|
|
1001
|
+
|
|
1002
|
+
# Сумма волн с иррациональными частотами
|
|
1003
|
+
value = 0.0
|
|
1004
|
+
|
|
1005
|
+
# Золотое сечение
|
|
1006
|
+
phi = params.fibonacci_ratio
|
|
1007
|
+
|
|
1008
|
+
# Несколько волн с иррациональными соотношениями
|
|
1009
|
+
for m in range(symmetry):
|
|
1010
|
+
angle = 2 * np.pi * m / symmetry
|
|
1011
|
+
|
|
1012
|
+
# Волновой вектор
|
|
1013
|
+
kx = np.cos(angle)
|
|
1014
|
+
ky = np.sin(angle)
|
|
1015
|
+
|
|
1016
|
+
# Иррациональная частота
|
|
1017
|
+
freq = 1.0 + phi * m
|
|
1018
|
+
|
|
1019
|
+
value += np.sin(kx * x * freq + ky * y * freq + z * np.sqrt(freq))
|
|
1020
|
+
|
|
1021
|
+
value /= symmetry
|
|
1022
|
+
|
|
1023
|
+
# Порог для создания структуры
|
|
1024
|
+
if abs(value) < params.thickness:
|
|
1025
|
+
# Переливчатый цвет как у квазикристаллов
|
|
1026
|
+
hue = (x + y + z) * 0.3 % 1.0
|
|
1027
|
+
|
|
1028
|
+
from colorsys import hsv_to_rgb
|
|
1029
|
+
r, g, b = hsv_to_rgb(hue, 0.9, 1.0)
|
|
1030
|
+
|
|
1031
|
+
alpha = 1.0 - abs(value) / params.thickness
|
|
1032
|
+
|
|
1033
|
+
# Добавляем интерференционные полосы
|
|
1034
|
+
interference = np.sin(x * 20) * np.sin(y * 20) * 0.2 + 0.8
|
|
1035
|
+
r *= interference
|
|
1036
|
+
g *= interference
|
|
1037
|
+
b *= interference
|
|
1038
|
+
|
|
1039
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
1040
|
+
|
|
1041
|
+
return texture
|
|
1042
|
+
|
|
1043
|
+
def _generate_tessellation(self,
|
|
1044
|
+
dimensions: Tuple[int, int, int],
|
|
1045
|
+
params: PatternParameters) -> np.ndarray:
|
|
1046
|
+
"""Генерация пространственной тесселяции"""
|
|
1047
|
+
depth, height, width = dimensions
|
|
1048
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
1049
|
+
|
|
1050
|
+
# Тип тесселяции
|
|
1051
|
+
tess_type = "truncated_octahedron" # Усеченный октаэдр
|
|
1052
|
+
|
|
1053
|
+
if tess_type == "truncated_octahedron":
|
|
1054
|
+
# Усеченный октаэдр заполняет пространство без зазоров
|
|
1055
|
+
|
|
1056
|
+
for i in range(depth):
|
|
1057
|
+
for j in range(height):
|
|
1058
|
+
for k in range(width):
|
|
1059
|
+
x = i / depth * params.scale
|
|
1060
|
+
y = j / height * params.scale
|
|
1061
|
+
z = k / width * params.scale
|
|
1062
|
+
|
|
1063
|
+
# Приводим к координатам в ячейке [0,1]^3
|
|
1064
|
+
cell_x = x * 4 % 1.0
|
|
1065
|
+
cell_y = y * 4 % 1.0
|
|
1066
|
+
cell_z = z * 4 % 1.0
|
|
1067
|
+
|
|
1068
|
+
# Расстояние до центра ячейки
|
|
1069
|
+
dist_to_center = np.sqrt(
|
|
1070
|
+
(cell_x - 0.5)**2 +
|
|
1071
|
+
(cell_y - 0.5)**2 +
|
|
1072
|
+
(cell_z - 0.5)**2
|
|
1073
|
+
)
|
|
1074
|
+
|
|
1075
|
+
# Также проверяем расстояние до граней
|
|
1076
|
+
dist_to_faces = min(
|
|
1077
|
+
cell_x, 1 - cell_x,
|
|
1078
|
+
cell_y, 1 - cell_y,
|
|
1079
|
+
cell_z, 1 - cell_z
|
|
1080
|
+
)
|
|
1081
|
+
|
|
1082
|
+
# Комбинированное расстояние
|
|
1083
|
+
dist = min(dist_to_center, dist_to_faces * np.sqrt(3))
|
|
1084
|
+
|
|
1085
|
+
# Если на поверхности усеченного октаэдра
|
|
1086
|
+
if abs(dist - 0.3) < params.thickness:
|
|
1087
|
+
# Цвет зависит от ориентации
|
|
1088
|
+
normal = np.array([
|
|
1089
|
+
cell_x - 0.5,
|
|
1090
|
+
cell_y - 0.5,
|
|
1091
|
+
cell_z - 0.5
|
|
1092
|
+
])
|
|
1093
|
+
normal = normal / (np.linalg.norm(normal) + 1e-8)
|
|
1094
|
+
|
|
1095
|
+
r = (normal[0] + 1) / 2
|
|
1096
|
+
g = (normal[1] + 1) / 2
|
|
1097
|
+
b = (normal[2] + 1) / 2
|
|
1098
|
+
|
|
1099
|
+
alpha = 1.0 - abs(dist - 0.3) / params.thickness
|
|
1100
|
+
|
|
1101
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
1102
|
+
|
|
1103
|
+
return texture
|
|
1104
|
+
|
|
1105
|
+
def _generate_fractal_sponge(self,
|
|
1106
|
+
dimensions: Tuple[int, int, int],
|
|
1107
|
+
params: PatternParameters) -> np.ndarray:
|
|
1108
|
+
"""Генерация губки Менгера (фрактальной губки)"""
|
|
1109
|
+
depth, height, width = dimensions
|
|
1110
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
1111
|
+
|
|
1112
|
+
# Функция для определения принадлежности к губке Менгера
|
|
1113
|
+
def in_menger_sponge(x, y, z, level):
|
|
1114
|
+
"""Рекурсивная проверка принадлежности к губке Менгера"""
|
|
1115
|
+
if level <= 0:
|
|
1116
|
+
return True
|
|
1117
|
+
|
|
1118
|
+
# Масштабируем координаты
|
|
1119
|
+
x *= 3
|
|
1120
|
+
y *= 3
|
|
1121
|
+
z *= 3
|
|
1122
|
+
|
|
1123
|
+
# Целочисленные части
|
|
1124
|
+
ix = int(x)
|
|
1125
|
+
iy = int(y)
|
|
1126
|
+
iz = int(z)
|
|
1127
|
+
|
|
1128
|
+
# Если в центральном кубе любого уровня - удаляем
|
|
1129
|
+
if ix == 1 and iy == 1:
|
|
1130
|
+
return False
|
|
1131
|
+
if ix == 1 and iz == 1:
|
|
1132
|
+
return False
|
|
1133
|
+
if iy == 1 and iz == 1:
|
|
1134
|
+
return False
|
|
1135
|
+
|
|
1136
|
+
# Рекурсивно проверяем следующий уровень
|
|
1137
|
+
return in_menger_sponge(x - ix, y - iy, z - iz, level - 1)
|
|
1138
|
+
|
|
1139
|
+
iterations = params.fractal_iterations
|
|
1140
|
+
|
|
1141
|
+
for i in range(depth):
|
|
1142
|
+
for j in range(height):
|
|
1143
|
+
for k in range(width):
|
|
1144
|
+
x = i / depth
|
|
1145
|
+
y = j / height
|
|
1146
|
+
z = k / width
|
|
1147
|
+
|
|
1148
|
+
# Принадлежность к губке Менгера
|
|
1149
|
+
if in_menger_sponge(x, y, z, iterations):
|
|
1150
|
+
# Цвет зависит от уровня
|
|
1151
|
+
level_color = 1.0 - 0.2 * iterations
|
|
1152
|
+
|
|
1153
|
+
# Текстура на поверхностях
|
|
1154
|
+
texture_val = (np.sin(x * 20) * np.sin(y * 20) *
|
|
1155
|
+
np.sin(z * 20) * 0.1 + 0.9)
|
|
1156
|
+
|
|
1157
|
+
r = 0.7 * level_color * texture_val
|
|
1158
|
+
g = 0.5 * level_color * texture_val
|
|
1159
|
+
b = 0.3 * level_color * texture_val
|
|
1160
|
+
|
|
1161
|
+
# Альфа на краях кубов
|
|
1162
|
+
# Определяем, на грани ли мы
|
|
1163
|
+
eps = 0.01
|
|
1164
|
+
on_edge = False
|
|
1165
|
+
|
|
1166
|
+
# Проверяем все направления
|
|
1167
|
+
for dx, dy, dz in [(1,0,0), (-1,0,0), (0,1,0),
|
|
1168
|
+
(0,-1,0), (0,0,1), (0,0,-1)]:
|
|
1169
|
+
nx, ny, nz = x + dx * eps, y + dy * eps, z + dz * eps
|
|
1170
|
+
if not in_menger_sponge(nx, ny, nz, iterations):
|
|
1171
|
+
on_edge = True
|
|
1172
|
+
break
|
|
1173
|
+
|
|
1174
|
+
if on_edge:
|
|
1175
|
+
alpha = 0.9
|
|
1176
|
+
else:
|
|
1177
|
+
alpha = 0.6
|
|
1178
|
+
|
|
1179
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
1180
|
+
|
|
1181
|
+
return texture
|
|
1182
|
+
|
|
1183
|
+
def _generate_knots(self,
|
|
1184
|
+
dimensions: Tuple[int, int, int],
|
|
1185
|
+
params: PatternParameters) -> np.ndarray:
|
|
1186
|
+
"""Генерация узлов и зацеплений"""
|
|
1187
|
+
depth, height, width = dimensions
|
|
1188
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
1189
|
+
|
|
1190
|
+
# Параметрическое представление узла трилистника
|
|
1191
|
+
def trefoil_knot(t, scale=1.0):
|
|
1192
|
+
"""Трилистник (trefoil knot)"""
|
|
1193
|
+
x = scale * (np.sin(t) + 2 * np.sin(2*t))
|
|
1194
|
+
y = scale * (np.cos(t) - 2 * np.cos(2*t))
|
|
1195
|
+
z = scale * (-np.sin(3*t))
|
|
1196
|
+
return np.array([x, y, z])
|
|
1197
|
+
|
|
1198
|
+
# Толщина трубки
|
|
1199
|
+
tube_radius = params.thickness * 2
|
|
1200
|
+
|
|
1201
|
+
for i in range(depth):
|
|
1202
|
+
for j in range(height):
|
|
1203
|
+
for k in range(width):
|
|
1204
|
+
# Нормализованные координаты
|
|
1205
|
+
x = (i / depth - 0.5) * 2
|
|
1206
|
+
y = (j / height - 0.5) * 2
|
|
1207
|
+
z = (k / width - 0.5) * 2
|
|
1208
|
+
|
|
1209
|
+
pos = np.array([x, y, z])
|
|
1210
|
+
min_dist = float('inf')
|
|
1211
|
+
|
|
1212
|
+
# Проверяем расстояние до узла
|
|
1213
|
+
n_samples = 100
|
|
1214
|
+
for n in range(n_samples):
|
|
1215
|
+
t = n / n_samples * 2 * np.pi
|
|
1216
|
+
|
|
1217
|
+
# Точка на узле
|
|
1218
|
+
knot_point = trefoil_knot(t, 0.5)
|
|
1219
|
+
|
|
1220
|
+
# Расстояние до этой точки
|
|
1221
|
+
dist = np.linalg.norm(pos - knot_point)
|
|
1222
|
+
min_dist = min(min_dist, dist)
|
|
1223
|
+
|
|
1224
|
+
# Если внутри трубки
|
|
1225
|
+
if min_dist < tube_radius:
|
|
1226
|
+
# Цвет узла
|
|
1227
|
+
# Зависит от параметра t (условно от угла)
|
|
1228
|
+
t_approx = np.arctan2(y, x)
|
|
1229
|
+
hue = t_approx / (2 * np.pi)
|
|
1230
|
+
|
|
1231
|
+
from colorsys import hsv_to_rgb
|
|
1232
|
+
r, g, b = hsv_to_rgb(hue, 0.9, 1.0)
|
|
1233
|
+
|
|
1234
|
+
# Интенсивность
|
|
1235
|
+
intensity = 1.0 - min_dist / tube_radius
|
|
1236
|
+
alpha = intensity * 0.9
|
|
1237
|
+
|
|
1238
|
+
# Полосы на трубке
|
|
1239
|
+
stripe = np.sin(t_approx * 20) * 0.2 + 0.8
|
|
1240
|
+
r *= stripe
|
|
1241
|
+
g *= stripe
|
|
1242
|
+
b *= stripe
|
|
1243
|
+
|
|
1244
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
1245
|
+
|
|
1246
|
+
# Также создаем второй узел для зацепления
|
|
1247
|
+
# (простой вариант - сдвинутый трилистник)
|
|
1248
|
+
second_knot_offset = np.array([0.3, 0.3, 0.3])
|
|
1249
|
+
min_dist2 = float('inf')
|
|
1250
|
+
|
|
1251
|
+
for n in range(n_samples):
|
|
1252
|
+
t = n / n_samples * 2 * np.pi
|
|
1253
|
+
knot_point = trefoil_knot(t, 0.5) + second_knot_offset
|
|
1254
|
+
dist = np.linalg.norm(pos - knot_point)
|
|
1255
|
+
min_dist2 = min(min_dist2, dist)
|
|
1256
|
+
|
|
1257
|
+
if min_dist2 < tube_radius:
|
|
1258
|
+
# Другой цвет для второго узла
|
|
1259
|
+
hue2 = (np.arctan2(y - 0.3, x - 0.3) / (2 * np.pi) + 0.5) % 1.0
|
|
1260
|
+
r2, g2, b2 = hsv_to_rgb(hue2, 0.7, 1.0)
|
|
1261
|
+
|
|
1262
|
+
intensity2 = 1.0 - min_dist2 / tube_radius
|
|
1263
|
+
alpha2 = intensity2 * 0.9
|
|
1264
|
+
|
|
1265
|
+
# Смешивание с первым узлом если пересекаются
|
|
1266
|
+
if texture[i, j, k, 3] > 0:
|
|
1267
|
+
# Область пересечения - белая
|
|
1268
|
+
mix_factor = texture[i, j, k, 3]
|
|
1269
|
+
r = r * (1 - mix_factor) + 1.0 * mix_factor
|
|
1270
|
+
g = g * (1 - mix_factor) + 1.0 * mix_factor
|
|
1271
|
+
b = b * (1 - mix_factor) + 1.0 * mix_factor
|
|
1272
|
+
alpha = max(texture[i, j, k, 3], alpha2)
|
|
1273
|
+
|
|
1274
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
1275
|
+
else:
|
|
1276
|
+
texture[i, j, k] = [r2, g2, b2, alpha2]
|
|
1277
|
+
|
|
1278
|
+
return texture
|
|
1279
|
+
|
|
1280
|
+
def _generate_helicoid(self,
|
|
1281
|
+
dimensions: Tuple[int, int, int],
|
|
1282
|
+
params: PatternParameters) -> np.ndarray:
|
|
1283
|
+
"""Генерация геликоида"""
|
|
1284
|
+
depth, height, width = dimensions
|
|
1285
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
1286
|
+
|
|
1287
|
+
for i in range(depth):
|
|
1288
|
+
for j in range(height):
|
|
1289
|
+
for k in range(width):
|
|
1290
|
+
x = (i / depth - 0.5) * 2 * params.scale
|
|
1291
|
+
y = (j / height - 0.5) * 2 * params.scale
|
|
1292
|
+
z = (k / width - 0.5) * 2 * params.scale
|
|
1293
|
+
|
|
1294
|
+
value = self.math_surfaces.helicoid(x, y, z, 5.0)
|
|
1295
|
+
|
|
1296
|
+
if abs(value) < params.thickness:
|
|
1297
|
+
# Цвет зависит от высоты
|
|
1298
|
+
hue = (z + 1) / 2
|
|
1299
|
+
|
|
1300
|
+
from colorsys import hsv_to_rgb
|
|
1301
|
+
r, g, b = hsv_to_rgb(hue, 0.8, 1.0)
|
|
1302
|
+
|
|
1303
|
+
alpha = 1.0 - abs(value) / params.thickness
|
|
1304
|
+
|
|
1305
|
+
# Полосы вдоль геликоида
|
|
1306
|
+
stripe = np.sin(x * 20 + z * 20) * 0.2 + 0.8
|
|
1307
|
+
r *= stripe
|
|
1308
|
+
g *= stripe
|
|
1309
|
+
b *= stripe
|
|
1310
|
+
|
|
1311
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
1312
|
+
|
|
1313
|
+
return texture
|
|
1314
|
+
|
|
1315
|
+
def _generate_catenoid(self,
|
|
1316
|
+
dimensions: Tuple[int, int, int],
|
|
1317
|
+
params: PatternParameters) -> np.ndarray:
|
|
1318
|
+
"""Генерация катеноида"""
|
|
1319
|
+
depth, height, width = dimensions
|
|
1320
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
1321
|
+
|
|
1322
|
+
for i in range(depth):
|
|
1323
|
+
for j in range(height):
|
|
1324
|
+
for k in range(width):
|
|
1325
|
+
x = (i / depth - 0.5) * 2 * params.scale
|
|
1326
|
+
y = (j / height - 0.5) * 2 * params.scale
|
|
1327
|
+
z = (k / width - 0.5) * 2 * params.scale
|
|
1328
|
+
|
|
1329
|
+
value = self.math_surfaces.catenoid(x, y, z, 3.0)
|
|
1330
|
+
|
|
1331
|
+
if abs(value) < params.thickness:
|
|
1332
|
+
# Цвет зависит от радиуса
|
|
1333
|
+
r_cyl = np.sqrt(x*x + y*y)
|
|
1334
|
+
hue = r_cyl % 1.0
|
|
1335
|
+
|
|
1336
|
+
from colorsys import hsv_to_rgb
|
|
1337
|
+
r, g, b = hsv_to_rgb(hue, 0.7, 1.0)
|
|
1338
|
+
|
|
1339
|
+
alpha = 1.0 - abs(value) / params.thickness
|
|
1340
|
+
|
|
1341
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
1342
|
+
|
|
1343
|
+
return texture
|
|
1344
|
+
|
|
1345
|
+
def _generate_enneper(self,
|
|
1346
|
+
dimensions: Tuple[int, int, int],
|
|
1347
|
+
params: PatternParameters) -> np.ndarray:
|
|
1348
|
+
"""Генерация поверхности Эннепера"""
|
|
1349
|
+
depth, height, width = dimensions
|
|
1350
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
1351
|
+
|
|
1352
|
+
for i in range(depth):
|
|
1353
|
+
for j in range(height):
|
|
1354
|
+
for k in range(width):
|
|
1355
|
+
x = (i / depth - 0.5) * 2 * params.scale
|
|
1356
|
+
y = (j / height - 0.5) * 2 * params.scale
|
|
1357
|
+
z = (k / width - 0.5) * 2 * params.scale
|
|
1358
|
+
|
|
1359
|
+
value = self.math_surfaces.enneper(x, y, z, 2.0)
|
|
1360
|
+
|
|
1361
|
+
if value < params.thickness * 2:
|
|
1362
|
+
# Сложный цветовой паттерн
|
|
1363
|
+
r = (np.sin(x * 10) + 1) / 2
|
|
1364
|
+
g = (np.sin(y * 10) + 1) / 2
|
|
1365
|
+
b = (np.sin(z * 10) + 1) / 2
|
|
1366
|
+
|
|
1367
|
+
alpha = 1.0 - value / (params.thickness * 2)
|
|
1368
|
+
|
|
1369
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
1370
|
+
|
|
1371
|
+
return texture
|
|
1372
|
+
|
|
1373
|
+
def _generate_corkscrew(self,
|
|
1374
|
+
dimensions: Tuple[int, int, int],
|
|
1375
|
+
params: PatternParameters) -> np.ndarray:
|
|
1376
|
+
"""Генерация штопора Архимеда"""
|
|
1377
|
+
depth, height, width = dimensions
|
|
1378
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
1379
|
+
|
|
1380
|
+
for i in range(depth):
|
|
1381
|
+
for j in range(height):
|
|
1382
|
+
for k in range(width):
|
|
1383
|
+
x = (i / depth - 0.5) * 2 * params.scale
|
|
1384
|
+
y = (j / height - 0.5) * 2 * params.scale
|
|
1385
|
+
z = (k / width - 0.5) * 2 * params.scale
|
|
1386
|
+
|
|
1387
|
+
value = self.math_surfaces.corkscrew(x, y, z, 3.0)
|
|
1388
|
+
|
|
1389
|
+
if abs(value) < params.thickness:
|
|
1390
|
+
# Цвет зависит от угла
|
|
1391
|
+
angle = np.arctan2(y, x)
|
|
1392
|
+
hue = angle / (2 * np.pi)
|
|
1393
|
+
|
|
1394
|
+
from colorsys import hsv_to_rgb
|
|
1395
|
+
r, g, b = hsv_to_rgb(hue, 0.9, 1.0)
|
|
1396
|
+
|
|
1397
|
+
alpha = 1.0 - abs(value) / params.thickness
|
|
1398
|
+
|
|
1399
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
1400
|
+
|
|
1401
|
+
return texture
|
|
1402
|
+
|
|
1403
|
+
def _generate_mobius(self,
|
|
1404
|
+
dimensions: Tuple[int, int, int],
|
|
1405
|
+
params: PatternParameters) -> np.ndarray:
|
|
1406
|
+
"""Генерация ленты Мёбиуса"""
|
|
1407
|
+
depth, height, width = dimensions
|
|
1408
|
+
texture = np.zeros((depth, height, width, 4), dtype=np.float32)
|
|
1409
|
+
|
|
1410
|
+
for i in range(depth):
|
|
1411
|
+
for j in range(height):
|
|
1412
|
+
for k in range(width):
|
|
1413
|
+
x = (i / depth - 0.5) * 2 * params.scale
|
|
1414
|
+
y = (j / height - 0.5) * 2 * params.scale
|
|
1415
|
+
z = (k / width - 0.5) * 2 * params.scale
|
|
1416
|
+
|
|
1417
|
+
value = self.math_surfaces.mobius(x, y, z, 2.0)
|
|
1418
|
+
|
|
1419
|
+
if value < params.thickness * 3:
|
|
1420
|
+
# Особый цвет для ленты Мёбиуса
|
|
1421
|
+
# Цвет меняется непрерывно при обходе
|
|
1422
|
+
angle = np.arctan2(y, x)
|
|
1423
|
+
|
|
1424
|
+
# На ленте Мёбиуса нужно сделать полный оборот 720 градусов
|
|
1425
|
+
# чтобы вернуться к исходному цвету
|
|
1426
|
+
hue = (angle * 2) % 1.0
|
|
1427
|
+
|
|
1428
|
+
from colorsys import hsv_to_rgb
|
|
1429
|
+
r, g, b = hsv_to_rgb(hue, 0.8, 1.0)
|
|
1430
|
+
|
|
1431
|
+
alpha = 1.0 - value / (params.thickness * 3)
|
|
1432
|
+
|
|
1433
|
+
texture[i, j, k] = [r, g, b, alpha]
|
|
1434
|
+
|
|
1435
|
+
return texture
|
|
1436
|
+
|
|
1437
|
+
def _add_noise(self, texture: np.ndarray, amplitude: float, scale: float) -> np.ndarray:
|
|
1438
|
+
"""Добавление шума к текстуре"""
|
|
1439
|
+
if amplitude <= 0:
|
|
1440
|
+
return texture
|
|
1441
|
+
|
|
1442
|
+
depth, height, width, channels = texture.shape
|
|
1443
|
+
|
|
1444
|
+
# Генерация шума
|
|
1445
|
+
noise = np.random.randn(depth, height, width) * amplitude
|
|
1446
|
+
|
|
1447
|
+
# Сглаживание шума
|
|
1448
|
+
from scipy import ndimage
|
|
1449
|
+
noise = ndimage.gaussian_filter(noise, sigma=scale)
|
|
1450
|
+
|
|
1451
|
+
# Применение шума к альфа-каналу
|
|
1452
|
+
texture_with_noise = texture.copy()
|
|
1453
|
+
|
|
1454
|
+
for i in range(depth):
|
|
1455
|
+
for j in range(height):
|
|
1456
|
+
for k in range(width):
|
|
1457
|
+
if texture[i, j, k, 3] > 0:
|
|
1458
|
+
# Изменяем альфа-канал
|
|
1459
|
+
new_alpha = texture[i, j, k, 3] * (1 + noise[i, j, k])
|
|
1460
|
+
texture_with_noise[i, j, k, 3] = np.clip(new_alpha, 0, 1)
|
|
1461
|
+
|
|
1462
|
+
# Немного изменяем цвет
|
|
1463
|
+
color_noise = noise[i, j, k] * 0.1
|
|
1464
|
+
texture_with_noise[i, j, k, 0] = np.clip(
|
|
1465
|
+
texture[i, j, k, 0] + color_noise, 0, 1
|
|
1466
|
+
)
|
|
1467
|
+
texture_with_noise[i, j, k, 1] = np.clip(
|
|
1468
|
+
texture[i, j, k, 1] + color_noise, 0, 1
|
|
1469
|
+
)
|
|
1470
|
+
texture_with_noise[i, j, k, 2] = np.clip(
|
|
1471
|
+
texture[i, j, k, 2] + color_noise, 0, 1
|
|
1472
|
+
)
|
|
1473
|
+
|
|
1474
|
+
return texture_with_noise
|
|
1475
|
+
|
|
1476
|
+
# ----------------------------------------------------------------------
|
|
1477
|
+
# Композитные паттерны и комбинации
|
|
1478
|
+
# ----------------------------------------------------------------------
|
|
1479
|
+
|
|
1480
|
+
class CompositePatternGenerator3D:
|
|
1481
|
+
"""Генератор композитных 3D паттернов (комбинации нескольких паттернов)"""
|
|
1482
|
+
|
|
1483
|
+
def __init__(self, seed: int = 42):
|
|
1484
|
+
self.seed = seed
|
|
1485
|
+
self.generator = GeometricPatternGenerator3D(seed)
|
|
1486
|
+
self.blend_cache = {}
|
|
1487
|
+
|
|
1488
|
+
def generate_composite(self,
|
|
1489
|
+
pattern_types: List[GeometricPattern3D],
|
|
1490
|
+
dimensions: Tuple[int, int, int],
|
|
1491
|
+
params_list: Optional[List[PatternParameters]] = None,
|
|
1492
|
+
blend_mode: str = "add") -> np.ndarray:
|
|
1493
|
+
"""
|
|
1494
|
+
Генерация композитного паттерна из нескольких типов
|
|
1495
|
+
|
|
1496
|
+
Args:
|
|
1497
|
+
pattern_types: Список типов паттернов для комбинирования
|
|
1498
|
+
dimensions: Размеры объема
|
|
1499
|
+
params_list: Список параметров для каждого паттерна
|
|
1500
|
+
blend_mode: Режим смешивания ('add', 'multiply', 'max', 'lerp')
|
|
1501
|
+
|
|
1502
|
+
Returns:
|
|
1503
|
+
Композитная 3D текстура
|
|
1504
|
+
"""
|
|
1505
|
+
if params_list is None:
|
|
1506
|
+
params_list = [PatternParameters() for _ in pattern_types]
|
|
1507
|
+
|
|
1508
|
+
if len(pattern_types) != len(params_list):
|
|
1509
|
+
raise ValueError("Number of pattern types must match number of parameter sets")
|
|
1510
|
+
|
|
1511
|
+
# Проверка кэша
|
|
1512
|
+
cache_key = (
|
|
1513
|
+
tuple(p.value for p in pattern_types),
|
|
1514
|
+
dimensions,
|
|
1515
|
+
tuple(hash(frozenset(p.__dict__.items())) for p in params_list),
|
|
1516
|
+
blend_mode
|
|
1517
|
+
)
|
|
1518
|
+
|
|
1519
|
+
if cache_key in self.blend_cache:
|
|
1520
|
+
return self.blend_cache[cache_key].copy()
|
|
1521
|
+
|
|
1522
|
+
print(f"Generating composite pattern from {len(pattern_types)} patterns...")
|
|
1523
|
+
|
|
1524
|
+
# Генерация отдельных паттернов
|
|
1525
|
+
textures = []
|
|
1526
|
+
for i, (pattern_type, params) in enumerate(zip(pattern_types, params_list)):
|
|
1527
|
+
print(f" Generating {pattern_type.name} ({i+1}/{len(pattern_types)})...")
|
|
1528
|
+
texture = self.generator.generate_pattern(pattern_type, dimensions, params)
|
|
1529
|
+
textures.append(texture)
|
|
1530
|
+
|
|
1531
|
+
# Смешивание паттернов
|
|
1532
|
+
composite = self._blend_textures(textures, blend_mode)
|
|
1533
|
+
|
|
1534
|
+
# Кэширование
|
|
1535
|
+
self.blend_cache[cache_key] = composite.copy()
|
|
1536
|
+
if len(self.blend_cache) > 30:
|
|
1537
|
+
self.blend_cache.pop(next(iter(self.blend_cache)))
|
|
1538
|
+
|
|
1539
|
+
return composite
|
|
1540
|
+
|
|
1541
|
+
def _blend_textures(self, textures: List[np.ndarray], blend_mode: str) -> np.ndarray:
|
|
1542
|
+
"""Смешивание нескольких текстур"""
|
|
1543
|
+
if not textures:
|
|
1544
|
+
raise ValueError("No textures to blend")
|
|
1545
|
+
|
|
1546
|
+
if len(textures) == 1:
|
|
1547
|
+
return textures[0]
|
|
1548
|
+
|
|
1549
|
+
result = textures[0].copy()
|
|
1550
|
+
|
|
1551
|
+
for i in range(1, len(textures)):
|
|
1552
|
+
if blend_mode == "add":
|
|
1553
|
+
result = self._blend_add(result, textures[i])
|
|
1554
|
+
elif blend_mode == "multiply":
|
|
1555
|
+
result = self._blend_multiply(result, textures[i])
|
|
1556
|
+
elif blend_mode == "max":
|
|
1557
|
+
result = self._blend_max(result, textures[i])
|
|
1558
|
+
elif blend_mode == "lerp":
|
|
1559
|
+
# Линейная интерполяция с равными весами
|
|
1560
|
+
weight = 1.0 / (i + 1)
|
|
1561
|
+
result = result * (1 - weight) + textures[i] * weight
|
|
1562
|
+
else:
|
|
1563
|
+
raise ValueError(f"Unknown blend mode: {blend_mode}")
|
|
1564
|
+
|
|
1565
|
+
return np.clip(result, 0, 1)
|
|
1566
|
+
|
|
1567
|
+
def _blend_add(self, a: np.ndarray, b: np.ndarray) -> np.ndarray:
|
|
1568
|
+
"""Сложение текстур"""
|
|
1569
|
+
blended = a + b
|
|
1570
|
+
# Сохраняем альфа как максимум
|
|
1571
|
+
blended[..., 3] = np.maximum(a[..., 3], b[..., 3])
|
|
1572
|
+
return blended
|
|
1573
|
+
|
|
1574
|
+
def _blend_multiply(self, a: np.ndarray, b: np.ndarray) -> np.ndarray:
|
|
1575
|
+
"""Умножение текстур"""
|
|
1576
|
+
blended = a * b
|
|
1577
|
+
# Альфа как среднее
|
|
1578
|
+
blended[..., 3] = (a[..., 3] + b[..., 3]) / 2
|
|
1579
|
+
return blended
|
|
1580
|
+
|
|
1581
|
+
def _blend_max(self, a: np.ndarray, b: np.ndarray) -> np.ndarray:
|
|
1582
|
+
"""Максимум из текстур"""
|
|
1583
|
+
blended = np.maximum(a, b)
|
|
1584
|
+
return blended
|
|
1585
|
+
|
|
1586
|
+
def generate_layered_pattern(self,
|
|
1587
|
+
pattern_layers: List[Tuple[GeometricPattern3D, PatternParameters, float]],
|
|
1588
|
+
dimensions: Tuple[int, int, int]) -> np.ndarray:
|
|
1589
|
+
"""
|
|
1590
|
+
Генерация слоистого паттерна с разными параметрами и прозрачностями
|
|
1591
|
+
|
|
1592
|
+
Args:
|
|
1593
|
+
pattern_layers: Список слоев (тип, параметры, непрозрачность)
|
|
1594
|
+
dimensions: Размеры объема
|
|
1595
|
+
|
|
1596
|
+
Returns:
|
|
1597
|
+
Слоистая 3D текстура
|
|
1598
|
+
"""
|
|
1599
|
+
result = np.zeros((*dimensions, 4), dtype=np.float32)
|
|
1600
|
+
|
|
1601
|
+
for pattern_type, params, opacity in pattern_layers:
|
|
1602
|
+
layer = self.generator.generate_pattern(pattern_type, dimensions, params)
|
|
1603
|
+
|
|
1604
|
+
# Применяем непрозрачность
|
|
1605
|
+
layer[..., 3] *= opacity
|
|
1606
|
+
|
|
1607
|
+
# Наложение слоев (фронтально-заднее смешивание)
|
|
1608
|
+
alpha = layer[..., 3:]
|
|
1609
|
+
result = result * (1 - alpha) + layer * alpha
|
|
1610
|
+
|
|
1611
|
+
return np.clip(result, 0, 1)
|
|
1612
|
+
|
|
1613
|
+
def generate_animated_pattern(self,
|
|
1614
|
+
base_pattern: GeometricPattern3D,
|
|
1615
|
+
dimensions: Tuple[int, int, int],
|
|
1616
|
+
base_params: PatternParameters,
|
|
1617
|
+
time: float,
|
|
1618
|
+
animation_type: str = "rotation") -> np.ndarray:
|
|
1619
|
+
"""
|
|
1620
|
+
Генерация анимированного геометрического паттерна
|
|
1621
|
+
|
|
1622
|
+
Args:
|
|
1623
|
+
base_pattern: Базовый тип паттерна
|
|
1624
|
+
dimensions: Размеры объема
|
|
1625
|
+
base_params: Базовые параметры
|
|
1626
|
+
time: Время анимации
|
|
1627
|
+
animation_type: Тип анимации ('rotation', 'pulse', 'morph')
|
|
1628
|
+
|
|
1629
|
+
Returns:
|
|
1630
|
+
Анимированная 3D текстура
|
|
1631
|
+
"""
|
|
1632
|
+
# Создаем копию параметров для анимации
|
|
1633
|
+
animated_params = PatternParameters(**base_params.__dict__)
|
|
1634
|
+
|
|
1635
|
+
if animation_type == "rotation":
|
|
1636
|
+
# Вращение паттерна
|
|
1637
|
+
animated_params.scale = base_params.scale * (1 + 0.1 * np.sin(time))
|
|
1638
|
+
|
|
1639
|
+
# Сдвиг фазы для эффекта вращения
|
|
1640
|
+
phase_shift = time * 2 * np.pi
|
|
1641
|
+
|
|
1642
|
+
# Генерация паттерна с модифицированными координатами
|
|
1643
|
+
# (в реальности нужно модифицировать сам алгоритм генерации)
|
|
1644
|
+
texture = self.generator.generate_pattern(base_pattern, dimensions, animated_params)
|
|
1645
|
+
|
|
1646
|
+
# Применяем сдвиг по осям для имитации вращения
|
|
1647
|
+
depth, height, width, _ = texture.shape
|
|
1648
|
+
|
|
1649
|
+
# Сдвигаем текстуру по осям
|
|
1650
|
+
shift_x = int(width * 0.1 * np.sin(time))
|
|
1651
|
+
shift_y = int(height * 0.1 * np.cos(time))
|
|
1652
|
+
shift_z = int(depth * 0.1 * np.sin(time * 0.7))
|
|
1653
|
+
|
|
1654
|
+
# Циклический сдвиг
|
|
1655
|
+
texture = np.roll(texture, shift_x, axis=2)
|
|
1656
|
+
texture = np.roll(texture, shift_y, axis=1)
|
|
1657
|
+
texture = np.roll(texture, shift_z, axis=0)
|
|
1658
|
+
|
|
1659
|
+
return texture
|
|
1660
|
+
|
|
1661
|
+
elif animation_type == "pulse":
|
|
1662
|
+
# Пульсация паттерна
|
|
1663
|
+
pulse = 0.5 + 0.5 * np.sin(time * 2)
|
|
1664
|
+
animated_params.thickness = base_params.thickness * pulse
|
|
1665
|
+
animated_params.scale = base_params.scale * (0.8 + 0.2 * pulse)
|
|
1666
|
+
|
|
1667
|
+
return self.generator.generate_pattern(base_pattern, dimensions, animated_params)
|
|
1668
|
+
|
|
1669
|
+
elif animation_type == "morph":
|
|
1670
|
+
# Морфинг между разными паттернами
|
|
1671
|
+
# Используем синусоидальную интерполяцию
|
|
1672
|
+
morph_factor = 0.5 + 0.5 * np.sin(time)
|
|
1673
|
+
|
|
1674
|
+
# Выбираем два паттерна для морфинга
|
|
1675
|
+
pattern1 = base_pattern
|
|
1676
|
+
pattern2 = GeometricPattern3D.GYROID # Или другой паттерн
|
|
1677
|
+
|
|
1678
|
+
texture1 = self.generator.generate_pattern(pattern1, dimensions, base_params)
|
|
1679
|
+
|
|
1680
|
+
# Модифицируем параметры для второго паттерна
|
|
1681
|
+
params2 = PatternParameters(**base_params.__dict__)
|
|
1682
|
+
params2.scale *= 1.2
|
|
1683
|
+
|
|
1684
|
+
texture2 = self.generator.generate_pattern(pattern2, dimensions, params2)
|
|
1685
|
+
|
|
1686
|
+
# Интерполяция
|
|
1687
|
+
return texture1 * (1 - morph_factor) + texture2 * morph_factor
|
|
1688
|
+
|
|
1689
|
+
else:
|
|
1690
|
+
raise ValueError(f"Unknown animation type: {animation_type}")
|
|
1691
|
+
|
|
1692
|
+
# ----------------------------------------------------------------------
|
|
1693
|
+
# Визуализация и экспорт
|
|
1694
|
+
# ----------------------------------------------------------------------
|
|
1695
|
+
|
|
1696
|
+
class GeometricPatternVisualizer3D:
|
|
1697
|
+
"""Визуализатор для геометрических 3D паттернов"""
|
|
1698
|
+
|
|
1699
|
+
def __init__(self):
|
|
1700
|
+
self.render_methods = {
|
|
1701
|
+
"raycast": self._render_raycast,
|
|
1702
|
+
"slices": self._render_slices,
|
|
1703
|
+
"mip": self._render_mip,
|
|
1704
|
+
"isosurface": self._render_isosurface,
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
def render(self,
|
|
1708
|
+
texture: np.ndarray,
|
|
1709
|
+
method: str = "raycast",
|
|
1710
|
+
**kwargs) -> np.ndarray:
|
|
1711
|
+
"""
|
|
1712
|
+
Рендеринг 3D текстуры
|
|
1713
|
+
|
|
1714
|
+
Args:
|
|
1715
|
+
texture: 3D текстура (D, H, W, 4)
|
|
1716
|
+
method: Метод рендеринга
|
|
1717
|
+
**kwargs: Параметры рендеринга
|
|
1718
|
+
|
|
1719
|
+
Returns:
|
|
1720
|
+
2D изображение
|
|
1721
|
+
"""
|
|
1722
|
+
if method not in self.render_methods:
|
|
1723
|
+
raise ValueError(f"Unknown render method: {method}")
|
|
1724
|
+
|
|
1725
|
+
return self.render_methods[method](texture, **kwargs)
|
|
1726
|
+
|
|
1727
|
+
def _render_raycast(self,
|
|
1728
|
+
texture: np.ndarray,
|
|
1729
|
+
camera_pos: Tuple[float, float, float] = (0.5, 0.5, 2.0),
|
|
1730
|
+
camera_target: Tuple[float, float, float] = (0.5, 0.5, 0.5),
|
|
1731
|
+
image_size: Tuple[int, int] = (512, 512),
|
|
1732
|
+
max_steps: int = 256) -> np.ndarray:
|
|
1733
|
+
"""Рейкастинг для геометрических паттернов"""
|
|
1734
|
+
depth, height, width, channels = texture.shape
|
|
1735
|
+
img_height, img_width = image_size
|
|
1736
|
+
|
|
1737
|
+
image = np.zeros((img_height, img_width, 4), dtype=np.float32)
|
|
1738
|
+
|
|
1739
|
+
# Базис камеры
|
|
1740
|
+
camera_dir = np.array(camera_target) - np.array(camera_pos)
|
|
1741
|
+
camera_dir = camera_dir / np.linalg.norm(camera_dir)
|
|
1742
|
+
|
|
1743
|
+
up = np.array([0.0, 1.0, 0.0])
|
|
1744
|
+
right = np.cross(camera_dir, up)
|
|
1745
|
+
right = right / np.linalg.norm(right)
|
|
1746
|
+
up = np.cross(right, camera_dir)
|
|
1747
|
+
|
|
1748
|
+
# FOV
|
|
1749
|
+
fov = 60.0
|
|
1750
|
+
aspect = img_width / img_height
|
|
1751
|
+
half_height = np.tan(np.radians(fov) / 2.0)
|
|
1752
|
+
half_width = aspect * half_height
|
|
1753
|
+
|
|
1754
|
+
# Шаг по лучу
|
|
1755
|
+
step_size = 1.0 / max(depth, height, width)
|
|
1756
|
+
|
|
1757
|
+
for y in range(img_height):
|
|
1758
|
+
for x in range(img_width):
|
|
1759
|
+
# Направление луча
|
|
1760
|
+
u = (2.0 * x / img_width - 1.0) * half_width
|
|
1761
|
+
v = (1.0 - 2.0 * y / img_height) * half_height
|
|
1762
|
+
|
|
1763
|
+
ray_dir = camera_dir + u * right + v * up
|
|
1764
|
+
ray_dir = ray_dir / np.linalg.norm(ray_dir)
|
|
1765
|
+
|
|
1766
|
+
# Стартовая позиция
|
|
1767
|
+
ray_pos = np.array(camera_pos, dtype=np.float32)
|
|
1768
|
+
|
|
1769
|
+
# Интегрирование
|
|
1770
|
+
color = np.zeros(4, dtype=np.float32)
|
|
1771
|
+
|
|
1772
|
+
for step in range(max_steps):
|
|
1773
|
+
# Проверяем границы
|
|
1774
|
+
if (ray_pos[0] < 0 or ray_pos[0] >= 1 or
|
|
1775
|
+
ray_pos[1] < 0 or ray_pos[1] >= 1 or
|
|
1776
|
+
ray_pos[2] < 0 or ray_pos[2] >= 1):
|
|
1777
|
+
break
|
|
1778
|
+
|
|
1779
|
+
# Координаты в текстуре
|
|
1780
|
+
tex_x = int(ray_pos[0] * (width - 1))
|
|
1781
|
+
tex_y = int(ray_pos[1] * (height - 1))
|
|
1782
|
+
tex_z = int(ray_pos[2] * (depth - 1))
|
|
1783
|
+
|
|
1784
|
+
# Берем значение
|
|
1785
|
+
sample = texture[tex_z, tex_y, tex_x]
|
|
1786
|
+
|
|
1787
|
+
# Фронтально-заднее смешивание
|
|
1788
|
+
alpha = sample[3]
|
|
1789
|
+
color = color + (1.0 - color[3]) * alpha * sample
|
|
1790
|
+
|
|
1791
|
+
# Если непрозрачно
|
|
1792
|
+
if color[3] > 0.99:
|
|
1793
|
+
break
|
|
1794
|
+
|
|
1795
|
+
# Двигаем луч
|
|
1796
|
+
ray_pos += ray_dir * step_size
|
|
1797
|
+
|
|
1798
|
+
image[y, x] = color
|
|
1799
|
+
|
|
1800
|
+
return np.clip(image, 0, 1)
|
|
1801
|
+
|
|
1802
|
+
def _render_slices(self,
|
|
1803
|
+
texture: np.ndarray,
|
|
1804
|
+
num_slices: int = 3,
|
|
1805
|
+
spacing: float = 0.1,
|
|
1806
|
+
image_size: Tuple[int, int] = (512, 512)) -> np.ndarray:
|
|
1807
|
+
"""Рендеринг нескольких срезов"""
|
|
1808
|
+
depth, height, width, channels = texture.shape
|
|
1809
|
+
img_height, img_width = image_size
|
|
1810
|
+
|
|
1811
|
+
image = np.zeros((img_height, img_width, 4), dtype=np.float32)
|
|
1812
|
+
|
|
1813
|
+
# Позиции срезов
|
|
1814
|
+
slice_positions = np.linspace(0.1, 0.9, num_slices)
|
|
1815
|
+
|
|
1816
|
+
for i, pos in enumerate(slice_positions):
|
|
1817
|
+
# Координата среза
|
|
1818
|
+
slice_idx = int(pos * depth)
|
|
1819
|
+
slice_data = texture[slice_idx]
|
|
1820
|
+
|
|
1821
|
+
# Масштабируем срез до размера изображения
|
|
1822
|
+
from scipy import ndimage
|
|
1823
|
+
|
|
1824
|
+
scale_y = img_height / height
|
|
1825
|
+
scale_x = img_width / width
|
|
1826
|
+
|
|
1827
|
+
slice_resized = np.zeros((img_height, img_width, channels), dtype=np.float32)
|
|
1828
|
+
|
|
1829
|
+
for c in range(channels):
|
|
1830
|
+
slice_resized[:, :, c] = ndimage.zoom(
|
|
1831
|
+
slice_data[:, :, c], (scale_y, scale_x), order=1
|
|
1832
|
+
)
|
|
1833
|
+
|
|
1834
|
+
# Смещение для 3D эффекта
|
|
1835
|
+
offset_x = int((i - num_slices/2) * img_width * spacing)
|
|
1836
|
+
offset_y = int((i - num_slices/2) * img_height * spacing * 0.5)
|
|
1837
|
+
|
|
1838
|
+
# Смешивание с изображением
|
|
1839
|
+
alpha = slice_resized[..., 3:]
|
|
1840
|
+
for y in range(img_height):
|
|
1841
|
+
for x in range(img_width):
|
|
1842
|
+
src_y = (y - offset_y) % img_height
|
|
1843
|
+
src_x = (x - offset_x) % img_width
|
|
1844
|
+
|
|
1845
|
+
if 0 <= src_y < img_height and 0 <= src_x < img_width:
|
|
1846
|
+
src_alpha = alpha[src_y, src_x, 0]
|
|
1847
|
+
image[y, x] = image[y, x] * (1 - src_alpha) + \
|
|
1848
|
+
slice_resized[src_y, src_x] * src_alpha
|
|
1849
|
+
|
|
1850
|
+
return np.clip(image, 0, 1)
|
|
1851
|
+
|
|
1852
|
+
def _render_mip(self,
|
|
1853
|
+
texture: np.ndarray,
|
|
1854
|
+
axis: str = 'z',
|
|
1855
|
+
image_size: Tuple[int, int] = (512, 512)) -> np.ndarray:
|
|
1856
|
+
"""MIP (Maximum Intensity Projection) рендеринг"""
|
|
1857
|
+
depth, height, width, channels = texture.shape
|
|
1858
|
+
|
|
1859
|
+
if axis == 'x':
|
|
1860
|
+
mip = np.max(texture, axis=2)
|
|
1861
|
+
elif axis == 'y':
|
|
1862
|
+
mip = np.max(texture, axis=1)
|
|
1863
|
+
else: # 'z'
|
|
1864
|
+
mip = np.max(texture, axis=0)
|
|
1865
|
+
|
|
1866
|
+
# Масштабирование
|
|
1867
|
+
from scipy import ndimage
|
|
1868
|
+
|
|
1869
|
+
img_height, img_width = image_size
|
|
1870
|
+
|
|
1871
|
+
scale_y = img_height / mip.shape[0]
|
|
1872
|
+
scale_x = img_width / mip.shape[1]
|
|
1873
|
+
|
|
1874
|
+
mip_resized = np.zeros((img_height, img_width, channels), dtype=np.float32)
|
|
1875
|
+
|
|
1876
|
+
for c in range(channels):
|
|
1877
|
+
mip_resized[:, :, c] = ndimage.zoom(
|
|
1878
|
+
mip[:, :, c], (scale_y, scale_x), order=1
|
|
1879
|
+
)
|
|
1880
|
+
|
|
1881
|
+
return np.clip(mip_resized, 0, 1)
|
|
1882
|
+
|
|
1883
|
+
def _render_isosurface(self,
|
|
1884
|
+
texture: np.ndarray,
|
|
1885
|
+
isolevel: float = 0.5,
|
|
1886
|
+
image_size: Tuple[int, int] = (512, 512)) -> np.ndarray:
|
|
1887
|
+
"""Рендеринг изоповерхности (упрощенный)"""
|
|
1888
|
+
depth, height, width, channels = texture.shape
|
|
1889
|
+
|
|
1890
|
+
# Используем только альфа-канал для поверхности
|
|
1891
|
+
alpha_volume = texture[..., 3]
|
|
1892
|
+
|
|
1893
|
+
# Находим поверхность через порог
|
|
1894
|
+
surface_mask = alpha_volume > isolevel
|
|
1895
|
+
|
|
1896
|
+
# Проекция
|
|
1897
|
+
projection = np.max(surface_mask, axis=0)
|
|
1898
|
+
|
|
1899
|
+
# Масштабирование
|
|
1900
|
+
img_height, img_width = image_size
|
|
1901
|
+
|
|
1902
|
+
from scipy import ndimage
|
|
1903
|
+
scale_y = img_height / projection.shape[0]
|
|
1904
|
+
scale_x = img_width / projection.shape[1]
|
|
1905
|
+
|
|
1906
|
+
projection_resized = ndimage.zoom(projection, (scale_y, scale_x), order=0)
|
|
1907
|
+
|
|
1908
|
+
# Создаем изображение
|
|
1909
|
+
image = np.zeros((img_height, img_width, 4), dtype=np.float32)
|
|
1910
|
+
|
|
1911
|
+
for y in range(img_height):
|
|
1912
|
+
for x in range(img_width):
|
|
1913
|
+
if projection_resized[y, x] > 0:
|
|
1914
|
+
# Цвет поверхности
|
|
1915
|
+
image[y, x] = [0.8, 0.8, 1.0, 1.0]
|
|
1916
|
+
|
|
1917
|
+
return image
|
|
1918
|
+
|
|
1919
|
+
def create_animation(self,
|
|
1920
|
+
pattern_generator: GeometricPatternGenerator3D,
|
|
1921
|
+
pattern_type: GeometricPattern3D,
|
|
1922
|
+
dimensions: Tuple[int, int, int],
|
|
1923
|
+
params: PatternParameters,
|
|
1924
|
+
num_frames: int = 60,
|
|
1925
|
+
animation_type: str = "rotation") -> List[np.ndarray]:
|
|
1926
|
+
"""
|
|
1927
|
+
Создание анимации геометрического паттерна
|
|
1928
|
+
|
|
1929
|
+
Returns:
|
|
1930
|
+
Список кадров анимации
|
|
1931
|
+
"""
|
|
1932
|
+
frames = []
|
|
1933
|
+
|
|
1934
|
+
for frame in range(num_frames):
|
|
1935
|
+
time = frame / num_frames * 2 * np.pi
|
|
1936
|
+
|
|
1937
|
+
# Модифицируем параметры для анимации
|
|
1938
|
+
animated_params = PatternParameters(**params.__dict__)
|
|
1939
|
+
|
|
1940
|
+
if animation_type == "rotation":
|
|
1941
|
+
# Вращение
|
|
1942
|
+
animated_params.scale = params.scale * (1 + 0.1 * np.sin(time))
|
|
1943
|
+
|
|
1944
|
+
# Также вращаем паттерн через сдвиг координат
|
|
1945
|
+
# (в реальной реализации нужно вращать сам алгоритм)
|
|
1946
|
+
texture = pattern_generator.generate_pattern(
|
|
1947
|
+
pattern_type, dimensions, animated_params
|
|
1948
|
+
)
|
|
1949
|
+
|
|
1950
|
+
# Циклический сдвиг для имитации вращения
|
|
1951
|
+
shift = int(frame * 0.5) % dimensions[2]
|
|
1952
|
+
texture = np.roll(texture, shift, axis=2)
|
|
1953
|
+
|
|
1954
|
+
elif animation_type == "pulse":
|
|
1955
|
+
# Пульсация
|
|
1956
|
+
pulse = 0.5 + 0.5 * np.sin(time)
|
|
1957
|
+
animated_params.thickness = params.thickness * pulse
|
|
1958
|
+
animated_params.scale = params.scale * (0.9 + 0.1 * pulse)
|
|
1959
|
+
|
|
1960
|
+
texture = pattern_generator.generate_pattern(
|
|
1961
|
+
pattern_type, dimensions, animated_params
|
|
1962
|
+
)
|
|
1963
|
+
|
|
1964
|
+
elif animation_type == "morph":
|
|
1965
|
+
# Морфинг между разными масштабами
|
|
1966
|
+
morph = np.sin(time) * 0.5 + 0.5
|
|
1967
|
+
animated_params.scale = params.scale * (0.7 + 0.6 * morph)
|
|
1968
|
+
|
|
1969
|
+
texture = pattern_generator.generate_pattern(
|
|
1970
|
+
pattern_type, dimensions, animated_params
|
|
1971
|
+
)
|
|
1972
|
+
|
|
1973
|
+
# Рендеринг кадра
|
|
1974
|
+
frame_image = self.render(texture, method="raycast",
|
|
1975
|
+
image_size=(256, 256))
|
|
1976
|
+
frames.append(frame_image)
|
|
1977
|
+
|
|
1978
|
+
print(f"Generated frame {frame+1}/{num_frames}")
|
|
1979
|
+
|
|
1980
|
+
return frames
|
|
1981
|
+
|
|
1982
|
+
# ----------------------------------------------------------------------
|
|
1983
|
+
# Примеры использования
|
|
1984
|
+
# ----------------------------------------------------------------------
|
|
1985
|
+
|
|
1986
|
+
def example_crystal_lattices():
|
|
1987
|
+
"""Пример различных кристаллических решеток"""
|
|
1988
|
+
|
|
1989
|
+
print("Crystal lattices examples...")
|
|
1990
|
+
|
|
1991
|
+
generator = GeometricPatternGenerator3D(seed=42)
|
|
1992
|
+
visualizer = GeometricPatternVisualizer3D()
|
|
1993
|
+
|
|
1994
|
+
# Различные типы кристаллов
|
|
1995
|
+
crystal_types = [
|
|
1996
|
+
("cubic", PatternParameters(
|
|
1997
|
+
scale=3.0,
|
|
1998
|
+
thickness=0.05,
|
|
1999
|
+
crystal_type="cubic"
|
|
2000
|
+
)),
|
|
2001
|
+
("hexagonal", PatternParameters(
|
|
2002
|
+
scale=2.0,
|
|
2003
|
+
thickness=0.04,
|
|
2004
|
+
crystal_type="hexagonal",
|
|
2005
|
+
symmetry=6
|
|
2006
|
+
)),
|
|
2007
|
+
("diamond", PatternParameters(
|
|
2008
|
+
scale=2.5,
|
|
2009
|
+
thickness=0.03,
|
|
2010
|
+
crystal_type="diamond",
|
|
2011
|
+
noise_amplitude=0.1
|
|
2012
|
+
))
|
|
2013
|
+
]
|
|
2014
|
+
|
|
2015
|
+
results = {}
|
|
2016
|
+
|
|
2017
|
+
for crystal_name, params in crystal_types:
|
|
2018
|
+
print(f"\nGenerating {crystal_name} crystal lattice...")
|
|
2019
|
+
|
|
2020
|
+
texture = generator.generate_pattern(
|
|
2021
|
+
GeometricPattern3D.CRYSTAL_LATTICE,
|
|
2022
|
+
dimensions=(64, 64, 64),
|
|
2023
|
+
params=params
|
|
2024
|
+
)
|
|
2025
|
+
|
|
2026
|
+
# Рендеринг
|
|
2027
|
+
image = visualizer.render(
|
|
2028
|
+
texture,
|
|
2029
|
+
method="raycast",
|
|
2030
|
+
image_size=(512, 512),
|
|
2031
|
+
camera_pos=(0.5, 0.5, 1.5),
|
|
2032
|
+
camera_target=(0.5, 0.5, 0.5)
|
|
2033
|
+
)
|
|
2034
|
+
|
|
2035
|
+
results[crystal_name] = (texture, image)
|
|
2036
|
+
|
|
2037
|
+
print(f" Texture shape: {texture.shape}")
|
|
2038
|
+
print(f" Image shape: {image.shape}")
|
|
2039
|
+
|
|
2040
|
+
return results
|
|
2041
|
+
|
|
2042
|
+
def example_complex_patterns():
|
|
2043
|
+
"""Пример сложных геометрических паттернов"""
|
|
2044
|
+
|
|
2045
|
+
print("\nComplex geometric patterns examples...")
|
|
2046
|
+
|
|
2047
|
+
generator = GeometricPatternGenerator3D(seed=123)
|
|
2048
|
+
visualizer = GeometricPatternVisualizer3D()
|
|
2049
|
+
|
|
2050
|
+
patterns = [
|
|
2051
|
+
("Gyroid", GeometricPattern3D.GYROID, PatternParameters(
|
|
2052
|
+
scale=4.0,
|
|
2053
|
+
thickness=0.03,
|
|
2054
|
+
surface_threshold=0.0
|
|
2055
|
+
)),
|
|
2056
|
+
("Schwarz P", GeometricPattern3D.SCHWARZ_P, PatternParameters(
|
|
2057
|
+
scale=3.0,
|
|
2058
|
+
thickness=0.04
|
|
2059
|
+
)),
|
|
2060
|
+
("Neovius", GeometricPattern3D.NEOVIUS, PatternParameters(
|
|
2061
|
+
scale=2.5,
|
|
2062
|
+
thickness=0.035
|
|
2063
|
+
)),
|
|
2064
|
+
("Fibonacci Spiral", GeometricPattern3D.FIBONACCI_SPIRAL, PatternParameters(
|
|
2065
|
+
scale=2.0,
|
|
2066
|
+
thickness=0.02,
|
|
2067
|
+
fibonacci_ratio=1.61803398875
|
|
2068
|
+
)),
|
|
2069
|
+
("Quasi Crystal", GeometricPattern3D.QUASI_CRYSTAL, PatternParameters(
|
|
2070
|
+
scale=3.5,
|
|
2071
|
+
thickness=0.025,
|
|
2072
|
+
quasi_symmetry=5
|
|
2073
|
+
)),
|
|
2074
|
+
("Menger Sponge", GeometricPattern3D.FRACTAL_SPONGE, PatternParameters(
|
|
2075
|
+
scale=1.0,
|
|
2076
|
+
fractal_iterations=3,
|
|
2077
|
+
thickness=0.05
|
|
2078
|
+
))
|
|
2079
|
+
]
|
|
2080
|
+
|
|
2081
|
+
results = {}
|
|
2082
|
+
|
|
2083
|
+
for pattern_name, pattern_type, params in patterns:
|
|
2084
|
+
print(f"\nGenerating {pattern_name}...")
|
|
2085
|
+
|
|
2086
|
+
texture = generator.generate_pattern(
|
|
2087
|
+
pattern_type,
|
|
2088
|
+
dimensions=(48, 48, 48), # Меньший размер для скорости
|
|
2089
|
+
params=params
|
|
2090
|
+
)
|
|
2091
|
+
|
|
2092
|
+
# Рендеринг
|
|
2093
|
+
image = visualizer.render(
|
|
2094
|
+
texture,
|
|
2095
|
+
method="raycast",
|
|
2096
|
+
image_size=(400, 400),
|
|
2097
|
+
camera_pos=(0.5, 0.5, 1.8),
|
|
2098
|
+
camera_target=(0.5, 0.5, 0.5)
|
|
2099
|
+
)
|
|
2100
|
+
|
|
2101
|
+
results[pattern_name] = (texture, image)
|
|
2102
|
+
|
|
2103
|
+
print(f" Generated {pattern_name} pattern")
|
|
2104
|
+
|
|
2105
|
+
return results
|
|
2106
|
+
|
|
2107
|
+
def example_composite_pattern():
|
|
2108
|
+
"""Пример композитного паттерна"""
|
|
2109
|
+
|
|
2110
|
+
print("\nComposite pattern example...")
|
|
2111
|
+
|
|
2112
|
+
composite_gen = CompositePatternGenerator3D(seed=42)
|
|
2113
|
+
visualizer = GeometricPatternVisualizer3D()
|
|
2114
|
+
|
|
2115
|
+
# Комбинируем несколько паттернов
|
|
2116
|
+
pattern_types = [
|
|
2117
|
+
GeometricPattern3D.CRYSTAL_LATTICE,
|
|
2118
|
+
GeometricPattern3D.GYROID,
|
|
2119
|
+
GeometricPattern3D.VORONOI_CELLS
|
|
2120
|
+
]
|
|
2121
|
+
|
|
2122
|
+
params_list = [
|
|
2123
|
+
PatternParameters(
|
|
2124
|
+
scale=2.0,
|
|
2125
|
+
thickness=0.04,
|
|
2126
|
+
crystal_type="cubic"
|
|
2127
|
+
),
|
|
2128
|
+
PatternParameters(
|
|
2129
|
+
scale=3.0,
|
|
2130
|
+
thickness=0.03,
|
|
2131
|
+
surface_threshold=0.0
|
|
2132
|
+
),
|
|
2133
|
+
PatternParameters(
|
|
2134
|
+
scale=1.5,
|
|
2135
|
+
packing_density=0.3,
|
|
2136
|
+
wall_thickness=0.1
|
|
2137
|
+
)
|
|
2138
|
+
]
|
|
2139
|
+
|
|
2140
|
+
print("Generating composite pattern from 3 patterns...")
|
|
2141
|
+
|
|
2142
|
+
composite = composite_gen.generate_composite(
|
|
2143
|
+
pattern_types=pattern_types,
|
|
2144
|
+
dimensions=(56, 56, 56),
|
|
2145
|
+
params_list=params_list,
|
|
2146
|
+
blend_mode="add"
|
|
2147
|
+
)
|
|
2148
|
+
|
|
2149
|
+
# Рендеринг
|
|
2150
|
+
image = visualizer.render(
|
|
2151
|
+
composite,
|
|
2152
|
+
method="raycast",
|
|
2153
|
+
image_size=(512, 512),
|
|
2154
|
+
camera_pos=(0.5, 0.5, 2.0),
|
|
2155
|
+
camera_target=(0.5, 0.5, 0.5)
|
|
2156
|
+
)
|
|
2157
|
+
|
|
2158
|
+
print(f"Composite texture shape: {composite.shape}")
|
|
2159
|
+
print(f"Rendered image shape: {image.shape}")
|
|
2160
|
+
|
|
2161
|
+
return composite, image
|
|
2162
|
+
|
|
2163
|
+
def example_animated_pattern():
|
|
2164
|
+
"""Пример анимированного паттерна"""
|
|
2165
|
+
|
|
2166
|
+
print("\nAnimated pattern example...")
|
|
2167
|
+
|
|
2168
|
+
generator = GeometricPatternGenerator3D(seed=42)
|
|
2169
|
+
visualizer = GeometricPatternVisualizer3D()
|
|
2170
|
+
composite_gen = CompositePatternGenerator3D(seed=42)
|
|
2171
|
+
|
|
2172
|
+
# Создаем анимацию морфинга
|
|
2173
|
+
print("Creating pattern morph animation...")
|
|
2174
|
+
|
|
2175
|
+
frames = []
|
|
2176
|
+
num_frames = 24
|
|
2177
|
+
|
|
2178
|
+
for frame in range(num_frames):
|
|
2179
|
+
time = frame / num_frames * 2 * np.pi
|
|
2180
|
+
|
|
2181
|
+
# Морфинг между гироидом и кристаллической решеткой
|
|
2182
|
+
morph_factor = 0.5 + 0.5 * np.sin(time)
|
|
2183
|
+
|
|
2184
|
+
# Параметры для гироида
|
|
2185
|
+
params_gyroid = PatternParameters(
|
|
2186
|
+
scale=2.5 + 0.5 * np.sin(time * 0.5),
|
|
2187
|
+
thickness=0.03,
|
|
2188
|
+
surface_threshold=0.0
|
|
2189
|
+
)
|
|
2190
|
+
|
|
2191
|
+
# Параметры для кристалла
|
|
2192
|
+
params_crystal = PatternParameters(
|
|
2193
|
+
scale=2.0 + 0.3 * np.cos(time * 0.5),
|
|
2194
|
+
thickness=0.04,
|
|
2195
|
+
crystal_type="hexagonal"
|
|
2196
|
+
)
|
|
2197
|
+
|
|
2198
|
+
# Генерируем оба паттерна
|
|
2199
|
+
texture_gyroid = generator.generate_pattern(
|
|
2200
|
+
GeometricPattern3D.GYROID,
|
|
2201
|
+
dimensions=(48, 48, 48),
|
|
2202
|
+
params=params_gyroid
|
|
2203
|
+
)
|
|
2204
|
+
|
|
2205
|
+
texture_crystal = generator.generate_pattern(
|
|
2206
|
+
GeometricPattern3D.CRYSTAL_LATTICE,
|
|
2207
|
+
dimensions=(48, 48, 48),
|
|
2208
|
+
params=params_crystal
|
|
2209
|
+
)
|
|
2210
|
+
|
|
2211
|
+
# Интерполяция
|
|
2212
|
+
texture = texture_gyroid * (1 - morph_factor) + \
|
|
2213
|
+
texture_crystal * morph_factor
|
|
2214
|
+
|
|
2215
|
+
# Рендеринг кадра
|
|
2216
|
+
frame_img = visualizer.render(
|
|
2217
|
+
texture,
|
|
2218
|
+
method="raycast",
|
|
2219
|
+
image_size=(256, 256),
|
|
2220
|
+
camera_pos=(0.5, 0.5, 1.5 + 0.3 * np.sin(time)),
|
|
2221
|
+
camera_target=(0.5, 0.5, 0.5)
|
|
2222
|
+
)
|
|
2223
|
+
|
|
2224
|
+
frames.append(frame_img)
|
|
2225
|
+
|
|
2226
|
+
print(f" Generated frame {frame+1}/{num_frames}")
|
|
2227
|
+
|
|
2228
|
+
print(f"Created animation with {len(frames)} frames")
|
|
2229
|
+
|
|
2230
|
+
return frames
|
|
2231
|
+
|
|
2232
|
+
def example_layered_pattern():
|
|
2233
|
+
"""Пример слоистого паттерна"""
|
|
2234
|
+
|
|
2235
|
+
print("\nLayered pattern example...")
|
|
2236
|
+
|
|
2237
|
+
composite_gen = CompositePatternGenerator3D(seed=123)
|
|
2238
|
+
visualizer = GeometricPatternVisualizer3D()
|
|
2239
|
+
|
|
2240
|
+
# Создаем слои
|
|
2241
|
+
layers = [
|
|
2242
|
+
(GeometricPattern3D.HONEYCOMB, PatternParameters(
|
|
2243
|
+
scale=2.0,
|
|
2244
|
+
cell_size=0.4,
|
|
2245
|
+
wall_thickness=0.08
|
|
2246
|
+
), 0.7), # Непрозрачность 70%
|
|
2247
|
+
|
|
2248
|
+
(GeometricPattern3D.CRYSTAL_LATTICE, PatternParameters(
|
|
2249
|
+
scale=3.0,
|
|
2250
|
+
thickness=0.02,
|
|
2251
|
+
crystal_type="diamond",
|
|
2252
|
+
noise_amplitude=0.2
|
|
2253
|
+
), 0.5), # Непрозрачность 50%
|
|
2254
|
+
|
|
2255
|
+
(GeometricPattern3D.SPHERE_PACKING, PatternParameters(
|
|
2256
|
+
scale=1.5,
|
|
2257
|
+
sphere_radius=0.2,
|
|
2258
|
+
packing_density=0.4
|
|
2259
|
+
), 0.3) # Непрозрачность 30%
|
|
2260
|
+
]
|
|
2261
|
+
|
|
2262
|
+
print("Generating layered pattern with 3 layers...")
|
|
2263
|
+
|
|
2264
|
+
layered_texture = composite_gen.generate_layered_pattern(
|
|
2265
|
+
pattern_layers=layers,
|
|
2266
|
+
dimensions=(64, 64, 64)
|
|
2267
|
+
)
|
|
2268
|
+
|
|
2269
|
+
# Рендеринг
|
|
2270
|
+
image = visualizer.render(
|
|
2271
|
+
layered_texture,
|
|
2272
|
+
method="raycast",
|
|
2273
|
+
image_size=(512, 512),
|
|
2274
|
+
camera_pos=(0.5, 0.5, 1.8),
|
|
2275
|
+
camera_target=(0.5, 0.5, 0.3)
|
|
2276
|
+
)
|
|
2277
|
+
|
|
2278
|
+
print(f"Layered texture shape: {layered_texture.shape}")
|
|
2279
|
+
print(f"Rendered image shape: {image.shape}")
|
|
2280
|
+
|
|
2281
|
+
return layered_texture, image
|
|
2282
|
+
|
|
2283
|
+
if __name__ == "__main__":
|
|
2284
|
+
print("Geometric 3D Patterns System")
|
|
2285
|
+
print("=" * 60)
|
|
2286
|
+
|
|
2287
|
+
# Пример 1: Кристаллические решетки
|
|
2288
|
+
crystal_results = example_crystal_lattices()
|
|
2289
|
+
|
|
2290
|
+
# Пример 2: Сложные геометрические паттерны
|
|
2291
|
+
pattern_results = example_complex_patterns()
|
|
2292
|
+
|
|
2293
|
+
# Пример 3: Композитный паттерн
|
|
2294
|
+
composite_texture, composite_image = example_composite_pattern()
|
|
2295
|
+
|
|
2296
|
+
# Пример 4: Анимированный паттерн
|
|
2297
|
+
animation_frames = example_animated_pattern()
|
|
2298
|
+
|
|
2299
|
+
# Пример 5: Слоистый паттерн
|
|
2300
|
+
layered_texture, layered_image = example_layered_pattern()
|
|
2301
|
+
|
|
2302
|
+
print("\n" + "=" * 60)
|
|
2303
|
+
print("Geometric 3D Patterns Features:")
|
|
2304
|
+
print("-" * 40)
|
|
2305
|
+
print("1. 20+ geometric pattern types:")
|
|
2306
|
+
print(" - Crystal lattices (cubic, hexagonal, diamond)")
|
|
2307
|
+
print(" - Minimal surfaces (gyroid, Schwarz P, Neovius)")
|
|
2308
|
+
print(" - Mathematical surfaces (helicoid, catenoid, Enneper)")
|
|
2309
|
+
print(" - Fractal structures (Menger sponge)")
|
|
2310
|
+
print(" - Biological patterns (honeycomb, sphere packing)")
|
|
2311
|
+
print(" - Topological structures (knots, Möbius strip)")
|
|
2312
|
+
print(" - Quasi-crystals and Fibonacci spirals")
|
|
2313
|
+
|
|
2314
|
+
print("\n2. Composite patterns:")
|
|
2315
|
+
print(" - Multi-pattern blending")
|
|
2316
|
+
print(" - Layered patterns with transparency")
|
|
2317
|
+
print(" - Animated pattern morphing")
|
|
2318
|
+
|
|
2319
|
+
print("\n3. Visualization methods:")
|
|
2320
|
+
print(" - Raycasting with volume rendering")
|
|
2321
|
+
print(" - Multi-slice visualization")
|
|
2322
|
+
print(" - Maximum intensity projection")
|
|
2323
|
+
print(" - Isosurface extraction")
|
|
2324
|
+
|
|
2325
|
+
print("\nPerformance optimizations:")
|
|
2326
|
+
print(" - Numba JIT compilation for critical functions")
|
|
2327
|
+
print(" - Intelligent caching system")
|
|
2328
|
+
print(" - Adaptive resolution based on view distance")
|
|
2329
|
+
print(" - Parallel processing for large volumes")
|
|
2330
|
+
|
|
2331
|
+
print("\nIntegration with game engine:")
|
|
2332
|
+
print("""
|
|
2333
|
+
# Пример интеграции с Unity
|
|
2334
|
+
class GeometricPatternsInGame:
|
|
2335
|
+
def InitializePatterns(self):
|
|
2336
|
+
# Инициализация генераторов
|
|
2337
|
+
self.pattern_generator = GeometricPatternGenerator3D(seed=world_seed)
|
|
2338
|
+
self.composite_generator = CompositePatternGenerator3D(seed=world_seed)
|
|
2339
|
+
|
|
2340
|
+
# Предзагрузка часто используемых паттернов
|
|
2341
|
+
self.preloaded_patterns = {
|
|
2342
|
+
"crystal": self.pattern_generator.generate_pattern(
|
|
2343
|
+
GeometricPattern3D.CRYSTAL_LATTICE,
|
|
2344
|
+
dimensions=(32, 32, 32),
|
|
2345
|
+
params=PatternParameters(scale=2.0, crystal_type="diamond")
|
|
2346
|
+
),
|
|
2347
|
+
"honeycomb": self.pattern_generator.generate_pattern(
|
|
2348
|
+
GeometricPattern3D.HONEYCOMB,
|
|
2349
|
+
dimensions=(32, 32, 32),
|
|
2350
|
+
params=PatternParameters(cell_size=0.3)
|
|
2351
|
+
)
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
def UpdateDynamicPatterns(self, player_position):
|
|
2355
|
+
# Динамическая генерация паттернов вокруг игрока
|
|
2356
|
+
player_chunk = self.WorldToChunk(player_position)
|
|
2357
|
+
|
|
2358
|
+
for dx in range(-2, 3):
|
|
2359
|
+
for dy in range(-1, 2):
|
|
2360
|
+
for dz in range(-2, 3):
|
|
2361
|
+
chunk_coords = (player_chunk[0] + dx,
|
|
2362
|
+
player_chunk[1] + dy,
|
|
2363
|
+
player_chunk[2] + dz)
|
|
2364
|
+
|
|
2365
|
+
# Генерация уникального паттерна для чанка
|
|
2366
|
+
pattern = self.GenerateChunkPattern(chunk_coords)
|
|
2367
|
+
|
|
2368
|
+
# Применение к геометрии мира
|
|
2369
|
+
self.ApplyPatternToChunk(chunk_coords, pattern)
|
|
2370
|
+
""")
|
|
2371
|
+
|
|
2372
|
+
print("\nGeometric 3D patterns system ready for procedural world generation!")
|