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.
@@ -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!")