fractex 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fractex/3d.py +1585 -0
- fractex/__init__.py +38 -0
- fractex/advanced.py +170 -0
- fractex/cli.py +81 -0
- fractex/core.py +508 -0
- fractex/dynamic_textures_3d.py +1935 -0
- fractex/examples/3d.py +109 -0
- fractex/examples/3d_integration.py +113 -0
- fractex/examples/3d_integration_2d.py +59 -0
- fractex/examples/__init__.py +34 -0
- fractex/examples/_output.py +115 -0
- fractex/examples/architecture_pattern.py +61 -0
- fractex/examples/atmosphere.py +54 -0
- fractex/examples/composite_material.py +63 -0
- fractex/examples/crystal_cave.py +61 -0
- fractex/examples/custom_pattern.py +114 -0
- fractex/examples/game_integration.py +86 -0
- fractex/examples/game_texture.py +178 -0
- fractex/examples/integration.py +102 -0
- fractex/examples/physic_integration.py +70 -0
- fractex/examples/splash.py +159 -0
- fractex/examples/terrain.py +76 -0
- fractex/examples/underwater.py +94 -0
- fractex/examples/underwater_volkano.py +112 -0
- fractex/geometric_patterns_3d.py +2372 -0
- fractex/interactive.py +158 -0
- fractex/simplex_noise.py +1113 -0
- fractex/texture_blending.py +1377 -0
- fractex/volume_scattering.py +1263 -0
- fractex/volume_textures.py +8 -0
- fractex-0.1.0.dist-info/METADATA +100 -0
- fractex-0.1.0.dist-info/RECORD +36 -0
- fractex-0.1.0.dist-info/WHEEL +5 -0
- fractex-0.1.0.dist-info/entry_points.txt +2 -0
- fractex-0.1.0.dist-info/licenses/LICENSE +21 -0
- fractex-0.1.0.dist-info/top_level.txt +1 -0
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
|