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/core.py ADDED
@@ -0,0 +1,508 @@
1
+ # fractex/core.py
2
+ """
3
+ Ядро для генерации бесконечно детализируемых фрактальных текстур
4
+ """
5
+
6
+ import numpy as np
7
+ from typing import Callable, Tuple, Optional, Dict, Any
8
+ from dataclasses import dataclass
9
+ from functools import lru_cache
10
+ import hashlib
11
+
12
+ @dataclass
13
+ class FractalParams:
14
+ """Параметры фрактальной текстуры"""
15
+ seed: int = 42
16
+ base_scale: float = 1.0
17
+ detail_level: float = 1.0 # Множитель детализации (1.0 = стандарт)
18
+ persistence: float = 0.5 # Сохранение энергии между октавами
19
+ lacunarity: float = 2.0 # Множитель частоты между октавами
20
+ octaves: int = 8 # Базовое количество октав
21
+ fractal_dimension: float = 2.3 # Фрактальная размерность
22
+ color_gradient: Optional[np.ndarray] = None
23
+
24
+ class FractalGenerator:
25
+ """Базовый генератор фрактальных текстур"""
26
+
27
+ def __init__(self, params: FractalParams = None):
28
+ self.params = params or FractalParams()
29
+ self._gradient_cache = {}
30
+ self._init_gradients()
31
+
32
+ def _init_gradients(self):
33
+ """Инициализация градиентов для шума"""
34
+ np.random.seed(self.params.seed)
35
+ # Градиенты для 2D шума
36
+ angles = np.random.rand(256) * 2 * np.pi
37
+ self.gradients = np.stack([np.cos(angles), np.sin(angles)], axis=1)
38
+
39
+ # Таблица перестановок для шума Перлина
40
+ self.perm = np.random.permutation(512)
41
+
42
+ def fractal_noise(self, x: np.ndarray, y: np.ndarray) -> np.ndarray:
43
+ """
44
+ Генерация фрактального шума (fBm - fractional Brownian motion)
45
+ с адаптивным количеством октав для бесконечной детализации
46
+ """
47
+ # Динамическое определение октав на основе требуемой детализации
48
+ effective_octaves = max(1, int(self.params.octaves * self.params.detail_level))
49
+
50
+ value = np.zeros_like(x)
51
+ amplitude = 1.0
52
+ frequency = self.params.base_scale
53
+
54
+ for i in range(effective_octaves):
55
+ # Добавляем октаву шума
56
+ noise = self._octave_noise(x * frequency, y * frequency, i)
57
+ value += amplitude * noise
58
+
59
+ # Обновляем параметры для следующей октавы
60
+ amplitude *= self.params.persistence
61
+ frequency *= self.params.lacunarity
62
+
63
+ # Если амплитуда слишком мала - останавливаемся
64
+ if amplitude < 0.001:
65
+ break
66
+
67
+ # Нормализация
68
+ max_val = (1 - self.params.persistence ** effective_octaves) / (1 - self.params.persistence)
69
+ return value / max_val
70
+
71
+ def _octave_noise(self, x: np.ndarray, y: np.ndarray, octave: int) -> np.ndarray:
72
+ """Генерация одной октавы шума с уникальным seed для октавы"""
73
+ # Используем хеш seed + octave для уникальности каждой октавы
74
+ octave_seed = hashlib.md5(f"{self.params.seed}_{octave}".encode()).hexdigest()
75
+ octave_seed_int = int(octave_seed, 16) % (2**32)
76
+
77
+ # Временное сохранение seed
78
+ original_state = np.random.get_state()
79
+ np.random.seed(octave_seed_int)
80
+
81
+ # Генерация симплекс-подобного шума
82
+ noise = self._simplex_noise_2d(x, y + octave * 100) # Сдвиг для уникальности
83
+
84
+ # Восстановление seed
85
+ np.random.set_state(original_state)
86
+
87
+ return noise
88
+
89
+ def _simplex_noise_2d(self, x: np.ndarray, y: np.ndarray) -> np.ndarray:
90
+ """Упрощенный 2D симплекс-шум для производительности"""
91
+ # Преобразуем в float для точности
92
+ x = x.astype(np.float64)
93
+ y = y.astype(np.float64)
94
+
95
+ # Константы для симплекс-шума
96
+ F2 = 0.5 * (np.sqrt(3.0) - 1.0)
97
+ G2 = (3.0 - np.sqrt(3.0)) / 6.0
98
+
99
+ # Скалярное произведение с градиентами
100
+ noise = np.zeros_like(x)
101
+
102
+ # Упрощенная реализация (полная реализация слишком длинная для примера)
103
+ # Здесь используем упрощенный шум для демонстрации
104
+ for i in range(x.shape[0]):
105
+ for j in range(x.shape[1]):
106
+ # Упрощенный псевдо-шум
107
+ nx = x[i, j] * 0.01
108
+ ny = y[i, j] * 0.01
109
+ noise[i, j] = np.sin(nx * 12.9898 + ny * 78.233) * 43758.5453
110
+ noise[i, j] = noise[i, j] - np.floor(noise[i, j])
111
+
112
+ return noise * 2 - 1 # Нормализация к [-1, 1]
113
+
114
+ class InfiniteTexture:
115
+ """Класс бесконечно детализируемой текстуры"""
116
+
117
+ def __init__(self, generator: FractalGenerator,
118
+ texture_type: str = "procedural"):
119
+ self.generator = generator
120
+ self.texture_type = texture_type
121
+ self._cache = {}
122
+
123
+ # Предопределенные типы текстур
124
+ self.texture_presets = {
125
+ "clouds": self._cloud_preset,
126
+ "marble": self._marble_preset,
127
+ "wood": self._wood_preset,
128
+ "stone": self._stone_preset,
129
+ "lava": self._lava_preset,
130
+ "water": self._water_preset,
131
+ }
132
+
133
+ def sample(self, x: float, y: float, zoom: float = 1.0,
134
+ channel: str = "all") -> np.ndarray:
135
+ """
136
+ Сэмплирование текстуры в точке (x, y) с заданным зумом
137
+
138
+ Args:
139
+ x, y: Координаты (могут быть дробными любого масштаба)
140
+ zoom: Уровень приближения (>1 = увеличение, <1 = уменьшение)
141
+ channel: 'r', 'g', 'b', 'a' или 'all'
142
+ """
143
+ # Адаптивная детализация на основе зума
144
+ detail_factor = max(0.1, zoom ** 0.7)
145
+
146
+ # Создаем небольшую область вокруг точки для сглаживания
147
+ eps = 0.001 / zoom
148
+ xx = np.array([[x - eps, x + eps]])
149
+ yy = np.array([[y - eps, y + eps]])
150
+
151
+ # Получаем базовый шум
152
+ base_noise = self.generator.fractal_noise(xx, yy)
153
+
154
+ # Применяем пресет текстуры
155
+ if self.texture_type in self.texture_presets:
156
+ texture_func = self.texture_presets[self.texture_type]
157
+ color = texture_func(base_noise, x, y, zoom)
158
+ else:
159
+ # По умолчанию: градиент от шума
160
+ color = self._default_texture(base_noise)
161
+
162
+ # Выбор канала
163
+ if channel != "all":
164
+ channel_idx = {"r": 0, "g": 1, "b": 2, "a": 3}[channel]
165
+ return color[..., channel_idx]
166
+
167
+ return color
168
+
169
+ def generate_tile(self, x0: float, y0: float, width: int, height: int,
170
+ zoom: float = 1.0) -> np.ndarray:
171
+ """Генерация тайла текстуры для заданной области"""
172
+ # Создаем координатную сетку
173
+ x = np.linspace(x0, x0 + width / zoom, width)
174
+ y = np.linspace(y0, y0 + height / zoom, height)
175
+ xx, yy = np.meshgrid(x, y)
176
+
177
+ # Генерация текстуры
178
+ texture = np.zeros((height, width, 4), dtype=np.float32)
179
+
180
+ # Используем стратифицированную выборку для больших тайлов
181
+ if width * height > 1000000: # Больше 1М пикселей
182
+ texture = self._generate_large_tile(xx, yy, zoom)
183
+ else:
184
+ noise = self.generator.fractal_noise(xx, yy)
185
+ if self.texture_type in self.texture_presets:
186
+ texture_func = self.texture_presets[self.texture_type]
187
+ texture = texture_func(noise, xx, yy, zoom)
188
+ else:
189
+ texture = self._default_texture(noise)
190
+
191
+ return np.clip(texture, 0, 1)
192
+
193
+ def _generate_large_tile(self, xx, yy, zoom):
194
+ """Оптимизированная генерация больших тайлов"""
195
+ height, width = xx.shape
196
+ texture = np.zeros((height, width, 4), dtype=np.float32)
197
+
198
+ # Разбиваем на блоки для кэширования
199
+ block_size = 64
200
+ for i in range(0, height, block_size):
201
+ for j in range(0, width, block_size):
202
+ i_end = min(i + block_size, height)
203
+ j_end = min(j + block_size, width)
204
+
205
+ block_key = f"{xx[i,j]:.6f}_{yy[i,j]:.6f}_{zoom:.3f}"
206
+ if block_key in self._cache:
207
+ texture[i:i_end, j:j_end] = self._cache[block_key]
208
+ else:
209
+ block_xx = xx[i:i_end, j:j_end]
210
+ block_yy = yy[i:i_end, j:j_end]
211
+ noise = self.generator.fractal_noise(block_xx, block_yy)
212
+
213
+ if self.texture_type in self.texture_presets:
214
+ texture_func = self.texture_presets[self.texture_type]
215
+ block_texture = texture_func(noise, block_xx, block_yy, zoom)
216
+ else:
217
+ block_texture = self._default_texture(noise)
218
+
219
+ texture[i:i_end, j:j_end] = block_texture
220
+ self._cache[block_key] = block_texture
221
+
222
+ # Ограничение размера кэша
223
+ if len(self._cache) > 100:
224
+ self._cache.pop(next(iter(self._cache)))
225
+
226
+ return texture
227
+
228
+ def _default_texture(self, noise):
229
+ """Текстура по умолчанию (градиент от шума)"""
230
+ noise_normalized = (noise + 1) / 2 # [0, 1]
231
+ height, width = noise.shape
232
+ texture = np.zeros((height, width, 4), dtype=np.float32)
233
+
234
+ # RGB каналы на основе шума
235
+ texture[..., 0] = noise_normalized # R
236
+ texture[..., 1] = np.abs(noise) # G
237
+ texture[..., 2] = 1 - noise_normalized # B
238
+ texture[..., 3] = 1.0 # Alpha
239
+
240
+ return texture
241
+
242
+ def _cloud_preset(self, noise, x, y, zoom):
243
+ """Пресет облачной текстуры"""
244
+ height, width = noise.shape
245
+ texture = np.zeros((height, width, 4), dtype=np.float32)
246
+
247
+ # Основной цвет облаков
248
+ cloud_base = (noise + 1) * 0.5
249
+ cloud_detail = np.sin(x * 0.1 + y * 0.1) * 0.2 + 0.5
250
+
251
+ # Смешивание слоев
252
+ clouds = np.clip(cloud_base * 0.7 + cloud_detail * 0.3, 0, 1)
253
+
254
+ texture[..., 0] = clouds # R
255
+ texture[..., 1] = clouds # G
256
+ texture[..., 2] = clouds * 0.9 + 0.1 # B (легкая голубизна)
257
+ texture[..., 3] = clouds * 0.8 + 0.2 # Alpha (полупрозрачность)
258
+
259
+ return texture
260
+
261
+ def _marble_preset(self, noise, x, y, zoom):
262
+ """Мраморная текстура"""
263
+ height, width = noise.shape
264
+ texture = np.zeros((height, width, 4), dtype=np.float32)
265
+
266
+ # Вены мрамора
267
+ veins = np.sin(x * 2 + noise * 5) * 0.5 + 0.5
268
+
269
+ # Базовый цвет мрамора
270
+ base_color = np.array([0.9, 0.85, 0.8]) # Светлый мрамор
271
+ vein_color = np.array([0.7, 0.6, 0.5]) # Темные вены
272
+
273
+ # Смешивание
274
+ mix_factor = veins ** 2
275
+ for i in range(3):
276
+ texture[..., i] = base_color[i] * (1 - mix_factor) + vein_color[i] * mix_factor
277
+
278
+ texture[..., 3] = 1.0 # Непрозрачный
279
+
280
+ return texture
281
+
282
+ def _wood_preset(self, noise, x, y, zoom):
283
+ """Деревянная текстура"""
284
+ height, width = noise.shape
285
+ texture = np.zeros((height, width, 4), dtype=np.float32)
286
+
287
+ rings = np.sin((x + y) * 0.1 + noise * 4.0) * 0.5 + 0.5
288
+ light_wood = np.array([0.7, 0.5, 0.3])
289
+ dark_wood = np.array([0.4, 0.25, 0.1])
290
+ for i in range(3):
291
+ texture[..., i] = light_wood[i] * rings + dark_wood[i] * (1 - rings)
292
+
293
+ texture[..., 3] = 1.0
294
+ return texture
295
+
296
+ def _stone_preset(self, noise, x, y, zoom):
297
+ """Каменная текстура"""
298
+ height, width = noise.shape
299
+ texture = np.zeros((height, width, 4), dtype=np.float32)
300
+
301
+ base = (noise + 1) * 0.5
302
+ texture[..., 0] = base * 0.6 + 0.2
303
+ texture[..., 1] = base * 0.6 + 0.2
304
+ texture[..., 2] = base * 0.6 + 0.2
305
+ texture[..., 3] = 1.0
306
+ return texture
307
+
308
+ def _lava_preset(self, noise, x, y, zoom):
309
+ """Лавовая текстура"""
310
+ height, width = noise.shape
311
+ texture = np.zeros((height, width, 4), dtype=np.float32)
312
+
313
+ lava = (noise + 1) * 0.5
314
+ texture[..., 0] = np.clip(lava * 1.5, 0, 1)
315
+ texture[..., 1] = np.clip(lava * 0.6, 0, 1)
316
+ texture[..., 2] = np.clip(lava * 0.1, 0, 0.2)
317
+ texture[..., 3] = 0.9 + lava * 0.1
318
+ return texture
319
+
320
+ def _water_preset(self, noise, x, y, zoom):
321
+ """Водная текстура"""
322
+ height, width = noise.shape
323
+ texture = np.zeros((height, width, 4), dtype=np.float32)
324
+
325
+ waves = np.sin(x * 0.2 + noise * 2.0) * 0.5 + 0.5
326
+ texture[..., 0] = 0.1 + waves * 0.1
327
+ texture[..., 1] = 0.2 + waves * 0.2
328
+ texture[..., 2] = 0.5 + waves * 0.3
329
+ texture[..., 3] = 0.8
330
+ return texture
331
+
332
+ # ------------------------------------------------------------
333
+ # Утилиты для работы с текстурами
334
+ # ------------------------------------------------------------
335
+
336
+ class TextureStreamer:
337
+ """Потоковая загрузка текстур с бесконечной детализацией"""
338
+
339
+ def __init__(self, base_texture: InfiniteTexture, cache_size_mb: int = 512):
340
+ self.base_texture = base_texture
341
+ self.cache_size_mb = cache_size_mb
342
+ self.tile_cache = {}
343
+ self.requests_queue = []
344
+
345
+ def request_tile(self, tile_x: int, tile_y: int, lod: int) -> np.ndarray:
346
+ """Запрос тайла текстуры определенного уровня детализации"""
347
+ tile_key = (tile_x, tile_y, lod)
348
+
349
+ if tile_key in self.tile_cache:
350
+ return self.tile_cache[tile_key]
351
+
352
+ # Генерация тайла
353
+ tile_size = 256 # Размер тайла в пикселях
354
+ scale = 2 ** lod # Масштаб на уровне детализации
355
+
356
+ x0 = tile_x * tile_size / scale
357
+ y0 = tile_y * tile_size / scale
358
+
359
+ tile = self.base_texture.generate_tile(
360
+ x0, y0, tile_size, tile_size, zoom=scale
361
+ )
362
+
363
+ # Кэширование
364
+ self.tile_cache[tile_key] = tile
365
+ self._manage_cache()
366
+
367
+ return tile
368
+
369
+ def _manage_cache(self):
370
+ """Управление кэшем (LRU)"""
371
+ max_tiles = (self.cache_size_mb * 1024 * 1024) // (256 * 256 * 4 * 4) # Примерный расчет
372
+ if len(self.tile_cache) > max_tiles:
373
+ # Удаляем самые старые тайлы
374
+ keys_to_remove = list(self.tile_cache.keys())[:len(self.tile_cache) - max_tiles]
375
+ for key in keys_to_remove:
376
+ del self.tile_cache[key]
377
+
378
+ # ------------------------------------------------------------
379
+ # Пример использования
380
+ # ------------------------------------------------------------
381
+
382
+ def create_example_scene():
383
+ """Создание демонстрационной сцены с бесконечно детализируемыми текстурами"""
384
+
385
+ # 1. Создаем генератор с параметрами
386
+ params = FractalParams(
387
+ seed=42,
388
+ base_scale=0.01,
389
+ detail_level=2.0,
390
+ persistence=0.6,
391
+ lacunarity=1.8,
392
+ octaves=12,
393
+ fractal_dimension=2.5
394
+ )
395
+
396
+ generator = FractalGenerator(params)
397
+
398
+ # 2. Создаем разные типы текстур
399
+ textures = {
400
+ "clouds": InfiniteTexture(generator, "clouds"),
401
+ "marble": InfiniteTexture(generator, "marble"),
402
+ "wood": InfiniteTexture(generator, "wood"),
403
+ "lava": InfiniteTexture(generator, "lava"),
404
+ }
405
+
406
+ # 3. Генерация тайлов на разных уровнях детализации
407
+ streamer = TextureStreamer(textures["clouds"])
408
+
409
+ # Пример: получение тайлов для квадранта
410
+ tiles = {}
411
+ for lod in range(4): # 4 уровня детализации
412
+ for tx in range(2):
413
+ for ty in range(2):
414
+ tile = streamer.request_tile(tx, ty, lod)
415
+ tiles[(tx, ty, lod)] = tile
416
+
417
+ return textures, tiles
418
+
419
+ # ------------------------------------------------------------
420
+ # Оптимизированный шум для производительности
421
+ # ------------------------------------------------------------
422
+
423
+ class OptimizedNoise:
424
+ """Оптимизированные алгоритмы шума для реального времени"""
425
+
426
+ @staticmethod
427
+ def fast_perlin(x, y, seed=0):
428
+ """Быстрая реализация шума Перлина"""
429
+ # Упрощенная версия для производительности
430
+ X = np.floor(x).astype(int) & 255
431
+ Y = np.floor(y).astype(int) & 255
432
+
433
+ xf = x - np.floor(x)
434
+ yf = y - np.floor(y)
435
+
436
+ # Функция сглаживания
437
+ u = xf * xf * xf * (xf * (xf * 6 - 15) + 10)
438
+ v = yf * yf * yf * (yf * (yf * 6 - 15) + 10)
439
+
440
+ # Градиенты в углах
441
+ np.random.seed(seed)
442
+ grad = np.random.randn(256, 256, 2)
443
+
444
+ # Скалярные произведения
445
+ dot00 = (xf) * grad[X, Y, 0] + (yf) * grad[X, Y, 1]
446
+ dot01 = (xf) * grad[X, Y+1, 0] + (yf-1) * grad[X, Y+1, 1]
447
+ dot10 = (xf-1) * grad[X+1, Y, 0] + (yf) * grad[X+1, Y, 1]
448
+ dot11 = (xf-1) * grad[X+1, Y+1, 0] + (yf-1) * grad[X+1, Y+1, 1]
449
+
450
+ # Интерполяция
451
+ x1 = dot00 + u * (dot10 - dot00)
452
+ x2 = dot01 + u * (dot11 - dot01)
453
+
454
+ return x1 + v * (x2 - x1)
455
+
456
+ if __name__ == "__main__":
457
+ # Демонстрация работы
458
+ print("Fractal Texture Engine Demo")
459
+ print("=" * 50)
460
+
461
+ # Создаем текстуру облаков
462
+ params = FractalParams(seed=123, detail_level=3.0)
463
+ generator = FractalGenerator(params)
464
+ cloud_texture = InfiniteTexture(generator, "clouds")
465
+
466
+ # Сэмплируем в одной точке с разным зумом
467
+ point = (10.5, 20.3)
468
+ for zoom in [1.0, 10.0, 100.0, 1000.0]:
469
+ color = cloud_texture.sample(point[0], point[1], zoom=zoom)
470
+ print(f"Zoom {zoom:6.1f}: Color = {color[0,0]}")
471
+
472
+ # Генерация тайла 512x512
473
+ print("\nGenerating 512x512 cloud tile...")
474
+ tile = cloud_texture.generate_tile(0, 0, 512, 512, zoom=1.0)
475
+ print(f"Tile shape: {tile.shape}, dtype: {tile.dtype}")
476
+ print(f"Min/Max: {tile.min():.3f}/{tile.max():.3f}")
477
+
478
+ # fractex/core.py - обновленный FractalGenerator
479
+
480
+ from .simplex_noise import SimplexNoise
481
+
482
+ class FractalGenerator:
483
+ def __init__(self, params=None):
484
+ self.params = params or FractalParams()
485
+ self.simplex = SimplexNoise(self.params.seed)
486
+
487
+ def fractal_noise(self, x, y):
488
+ """Используем симплекс-шум вместо упрощенного"""
489
+ effective_octaves = max(1, int(self.params.octaves * self.params.detail_level))
490
+
491
+ value = np.zeros_like(x)
492
+ amplitude = 1.0
493
+ frequency = self.params.base_scale
494
+
495
+ for i in range(effective_octaves):
496
+ # Используем симплекс-шум
497
+ noise = self.simplex.noise_2d(x * frequency, y * frequency)
498
+ value += amplitude * noise
499
+
500
+ amplitude *= self.params.persistence
501
+ frequency *= self.params.lacunarity
502
+
503
+ if amplitude < 0.001:
504
+ break
505
+
506
+ # Нормализация
507
+ max_val = (1 - self.params.persistence ** effective_octaves) / (1 - self.params.persistence)
508
+ return value / max_val if max_val > 0 else value