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,114 @@
1
+ # Создание собственных паттернов
2
+ import sys
3
+ from pathlib import Path
4
+ from dataclasses import dataclass
5
+ import numpy as np
6
+ import argparse
7
+
8
+ ROOT = Path(__file__).resolve().parents[2]
9
+ if str(ROOT) not in sys.path:
10
+ sys.path.insert(0, str(ROOT))
11
+ EXAMPLES_DIR = Path(__file__).resolve().parent
12
+ if str(EXAMPLES_DIR) not in sys.path:
13
+ sys.path.insert(0, str(EXAMPLES_DIR))
14
+
15
+ from _output import save_volume_slice
16
+ from fractex.interactive import add_interactive_args, InteractiveConfig, run_interactive
17
+
18
+
19
+ @dataclass
20
+ class CustomPatternParams:
21
+ scale: float = 10.0
22
+ surface_threshold: float = 0.5
23
+
24
+
25
+ class CustomPatternGenerator:
26
+ def generate_custom_pattern(self, dimensions, params: CustomPatternParams):
27
+ depth, height, width = dimensions
28
+ texture = np.zeros((depth, height, width, 4), dtype=np.float32)
29
+
30
+ # Пользовательская логика
31
+ for i in range(depth):
32
+ for j in range(height):
33
+ for k in range(width):
34
+ value = self.custom_math_function(i, j, k, params)
35
+
36
+ if self.is_on_surface(value, params):
37
+ color = self.calculate_color(i, j, k, value)
38
+ texture[i, j, k] = color
39
+
40
+ return texture
41
+
42
+ def custom_math_function(self, x, y, z, params: CustomPatternParams):
43
+ # Пример: гиперболический параболоид
44
+ return (x / params.scale) ** 2 - (y / params.scale) ** 2 - z / params.scale
45
+
46
+ def is_on_surface(self, value: float, params: CustomPatternParams) -> bool:
47
+ return abs(value) < params.surface_threshold
48
+
49
+ def calculate_color(self, x, y, z, value: float) -> np.ndarray:
50
+ return np.array([
51
+ 0.4 + 0.6 * np.clip(value, -1, 1),
52
+ 0.6,
53
+ 0.8,
54
+ 1.0,
55
+ ], dtype=np.float32)
56
+
57
+
58
+ if __name__ == "__main__":
59
+ parser = argparse.ArgumentParser(description="Custom pattern example")
60
+ add_interactive_args(parser)
61
+ parser.add_argument("--speed", type=float, default=1.0)
62
+ args = parser.parse_args()
63
+
64
+ generator = CustomPatternGenerator()
65
+ params = CustomPatternParams(scale=8.0, surface_threshold=0.4)
66
+ texture = generator.generate_custom_pattern((16, 16, 16), params)
67
+ print("Custom pattern:", texture.shape, texture.min(), texture.max())
68
+ save_volume_slice(
69
+ texture,
70
+ EXAMPLES_DIR / "output" / "custom_pattern_slice.ppm",
71
+ stretch=True,
72
+ )
73
+
74
+ if args.interactive:
75
+ config = InteractiveConfig.from_args(args, title="Custom Pattern (interactive)")
76
+
77
+ def render_frame(t, w, h):
78
+ speed = max(0.1, args.speed)
79
+ tt = t * speed * 3.0
80
+ zoom = np.exp(tt * 0.03)
81
+ zoom = min(zoom, 200.0)
82
+ depth = (tt * 1.5) % (params.scale * 6.0)
83
+ drift = np.exp(-tt * 0.02)
84
+ x0 = np.sin(tt * 0.4) * 3.0 * drift
85
+ y0 = np.cos(tt * 0.35) * 3.0 * drift
86
+
87
+ x = np.linspace(-1.0, 1.0, w) / zoom + x0
88
+ y = np.linspace(-1.0, 1.0, h) / zoom + y0
89
+ xx, yy = np.meshgrid(x, y)
90
+
91
+ angle = t * 0.02
92
+ cos_a, sin_a = np.cos(angle), np.sin(angle)
93
+ xr = xx * cos_a - yy * sin_a
94
+ yr = xx * sin_a + yy * cos_a
95
+
96
+ value = (xr / params.scale) ** 2 - (yr / params.scale) ** 2 - depth / params.scale
97
+ value2 = np.sin(xr * 3.0 + t * 0.5) + np.cos(yr * 2.0 - t * 0.3)
98
+ combined = value + 0.35 * value2
99
+ mask = 0.1 + 0.9 * np.exp(-np.abs(combined) * 1.6)
100
+
101
+ radius = np.sqrt(xr * xr + yr * yr)
102
+ vignette = np.clip(1.2 - radius * 0.8, 0.0, 1.0)
103
+
104
+ rgb = np.zeros((h, w, 3), dtype=np.float32)
105
+ color = np.clip(0.4 + 0.6 * np.tanh(combined), 0, 1)
106
+ ripple = 0.5 + 0.5 * np.sin(t * 0.6 + (xr + yr) * 2.0)
107
+ palette_shift = 0.5 + 0.5 * np.sin(t * 0.12)
108
+ rgb[..., 0] = (color * 0.7 + ripple * 0.3) * mask
109
+ rgb[..., 1] = (0.45 + 0.45 * ripple + 0.1 * palette_shift) * mask
110
+ rgb[..., 2] = (0.65 + 0.3 * np.cos(t * 0.4) + 0.1 * palette_shift) * mask
111
+ rgb = np.clip(rgb * vignette[..., None], 0, 1)
112
+ return rgb
113
+
114
+ run_interactive(render_frame, config)
@@ -0,0 +1,86 @@
1
+ # Полная интеграция с Unity/Unreal
2
+ import sys
3
+ from pathlib import Path
4
+ import math
5
+ from typing import Tuple, List
6
+
7
+ ROOT = Path(__file__).resolve().parents[2]
8
+ if str(ROOT) not in sys.path:
9
+ sys.path.insert(0, str(ROOT))
10
+
11
+ from fractex.dynamic_textures_3d import StreamingDynamicTextures, DynamicTextureType
12
+
13
+
14
+ class GameDynamicTexturesManager:
15
+ def __init__(self, world_size: Tuple[int, int, int]):
16
+ # Разделяем мир на чанки
17
+ self.chunk_size = (32, 32, 32)
18
+ self.num_chunks = (
19
+ world_size[0] // self.chunk_size[0],
20
+ world_size[1] // self.chunk_size[1],
21
+ world_size[2] // self.chunk_size[2],
22
+ )
23
+
24
+ self.streamer = StreamingDynamicTextures(
25
+ chunk_size=self.chunk_size,
26
+ max_active_chunks=8
27
+ )
28
+
29
+ self.render_cache = {}
30
+
31
+ def update(self, dt: float, player_position: Tuple[float, float, float]):
32
+ visible_chunks = self._get_visible_chunks(player_position)
33
+
34
+ for chunk_coords in visible_chunks:
35
+ distance = self._distance_to_chunk(player_position, chunk_coords)
36
+ priority = 1.0 / (distance + 1.0)
37
+
38
+ state = self.streamer.request_chunk(
39
+ chunk_coords,
40
+ DynamicTextureType.WATER_FLOW,
41
+ priority
42
+ )
43
+
44
+ if state:
45
+ self._render_or_update_chunk(chunk_coords, state)
46
+
47
+ try:
48
+ self.streamer.update_all(dt)
49
+ except Exception as exc:
50
+ print(f"Simulation update skipped: {exc}")
51
+
52
+ def _get_visible_chunks(self, player_position: Tuple[float, float, float]) -> List[Tuple[int, int, int]]:
53
+ """Получение чанков в поле зрения игрока (упрощенно)"""
54
+ px, py, pz = player_position
55
+ cx = int(px // self.chunk_size[0])
56
+ cy = int(py // self.chunk_size[1])
57
+ cz = int(pz // self.chunk_size[2])
58
+
59
+ visible = []
60
+ for dx in [-1, 0, 1]:
61
+ for dy in [-1, 0, 1]:
62
+ for dz in [-1, 0, 1]:
63
+ nx, ny, nz = cx + dx, cy + dy, cz + dz
64
+ if 0 <= nx < self.num_chunks[0] and 0 <= ny < self.num_chunks[1] and 0 <= nz < self.num_chunks[2]:
65
+ visible.append((nx, ny, nz))
66
+
67
+ return visible
68
+
69
+ def _distance_to_chunk(self, player_position: Tuple[float, float, float], chunk_coords: Tuple[int, int, int]) -> float:
70
+ px, py, pz = player_position
71
+ cx, cy, cz = chunk_coords
72
+ center = (
73
+ (cx + 0.5) * self.chunk_size[0],
74
+ (cy + 0.5) * self.chunk_size[1],
75
+ (cz + 0.5) * self.chunk_size[2],
76
+ )
77
+ return math.dist(player_position, center)
78
+
79
+ def _render_or_update_chunk(self, chunk_coords: Tuple[int, int, int], state):
80
+ self.render_cache[chunk_coords] = state.data.mean()
81
+
82
+
83
+ if __name__ == "__main__":
84
+ manager = GameDynamicTexturesManager(world_size=(96, 96, 96))
85
+ manager.update(dt=0.016, player_position=(48.0, 48.0, 48.0))
86
+ print("Render cache size:", len(manager.render_cache))
@@ -0,0 +1,178 @@
1
+ # fractex/examples/game_texture.py
2
+ """
3
+ Пример интеграции в игровой движок
4
+ """
5
+
6
+ import sys
7
+ from pathlib import Path
8
+ from queue import Queue
9
+ import threading
10
+ import argparse
11
+ import numpy as np
12
+
13
+ ROOT = Path(__file__).resolve().parents[2]
14
+ if str(ROOT) not in sys.path:
15
+ sys.path.insert(0, str(ROOT))
16
+ EXAMPLES_DIR = Path(__file__).resolve().parent
17
+ if str(EXAMPLES_DIR) not in sys.path:
18
+ sys.path.insert(0, str(EXAMPLES_DIR))
19
+
20
+ from fractex import FractalParams, FractalGenerator, InfiniteTexture, TextureStreamer
21
+ from _output import save_ppm
22
+ from fractex.interactive import add_interactive_args, InteractiveConfig, run_interactive
23
+
24
+
25
+ class GameTextureSystem:
26
+ """Система текстур для игры с бесконечной детализацией"""
27
+
28
+ def __init__(self):
29
+ self.textures = {}
30
+ self.streamers = {}
31
+ self.worker_threads = []
32
+ self.task_queue = Queue()
33
+
34
+ # Запускаем рабочие потоки для генерации текстур
35
+ for i in range(4): # 4 потока
36
+ thread = threading.Thread(target=self._texture_worker)
37
+ thread.daemon = True
38
+ thread.start()
39
+ self.worker_threads.append(thread)
40
+
41
+ def register_texture(self, name, params, texture_type="procedural"):
42
+ """Регистрация новой текстуры"""
43
+ generator = FractalGenerator(params)
44
+ texture = InfiniteTexture(generator, texture_type)
45
+ streamer = TextureStreamer(texture)
46
+
47
+ self.textures[name] = texture
48
+ self.streamers[name] = streamer
49
+
50
+ return texture
51
+
52
+ def request_texture_tiles(self, name, viewport, camera_zoom):
53
+ """Запрос тайлов текстуры для вьюпорта"""
54
+ streamer = self.streamers[name]
55
+
56
+ # Определяем необходимые тайлы и уровни детализации
57
+ tiles_needed = self._calculate_tiles_needed(viewport, camera_zoom)
58
+
59
+ # Запрашиваем все необходимые тайлы
60
+ results = {}
61
+ for tile_info in tiles_needed:
62
+ tile_x, tile_y, lod = tile_info
63
+
64
+ # Проверяем кэш
65
+ tile = streamer.request_tile(tile_x, tile_y, lod)
66
+ results[(tile_x, tile_y, lod)] = tile
67
+
68
+ return results
69
+
70
+ def _calculate_tiles_needed(self, viewport, zoom):
71
+ """Расчет необходимых тайлов на основе положения камеры"""
72
+ tiles = []
73
+
74
+ # Преобразуем вьюпорт в тайловые координаты
75
+ min_x, min_y = viewport['min']
76
+ max_x, max_y = viewport['max']
77
+
78
+ # Разные LOD для разных расстояний (MIP-маппинг)
79
+ base_tile_size = 256
80
+ for lod in range(4): # 4 уровня детализации
81
+ tile_scale = 2 ** lod
82
+ effective_tile_size = base_tile_size / tile_scale
83
+
84
+ start_tile_x = int(min_x // effective_tile_size)
85
+ start_tile_y = int(min_y // effective_tile_size)
86
+ end_tile_x = int(max_x // effective_tile_size) + 1
87
+ end_tile_y = int(max_y // effective_tile_size) + 1
88
+
89
+ for tx in range(start_tile_x, end_tile_x):
90
+ for ty in range(start_tile_y, end_tile_y):
91
+ tiles.append((tx, ty, lod))
92
+
93
+ return tiles
94
+
95
+ def _texture_worker(self):
96
+ """Рабочий поток для генерации текстур"""
97
+ while True:
98
+ try:
99
+ task = self.task_queue.get()
100
+ if task is None:
101
+ break
102
+
103
+ texture_name, tile_x, tile_y, lod = task
104
+ streamer = self.streamers[texture_name]
105
+ streamer.request_tile(tile_x, tile_y, lod)
106
+
107
+ self.task_queue.task_done()
108
+ except:
109
+ pass
110
+
111
+ # Демонстрация
112
+ def demo_terrain_texture():
113
+ """Создание бесконечно детализируемой текстуры terrain"""
114
+
115
+ # Параметры для terrain текстуры
116
+ terrain_params = FractalParams(
117
+ seed=42,
118
+ base_scale=0.005,
119
+ detail_level=4.0,
120
+ persistence=0.55,
121
+ lacunarity=2.1,
122
+ octaves=16,
123
+ fractal_dimension=2.7
124
+ )
125
+
126
+ # Создаем систему текстур
127
+ texture_system = GameTextureSystem()
128
+
129
+ # Регистрируем различные текстуры
130
+ texture_system.register_texture("terrain", terrain_params, "stone")
131
+ texture_system.register_texture("clouds",
132
+ FractalParams(seed=123, persistence=0.7, octaves=8), "clouds")
133
+ texture_system.register_texture("water",
134
+ FractalParams(seed=456, base_scale=0.002, persistence=0.4), "water")
135
+
136
+ # Имитация запросов от игрового движка
137
+ viewport = {'min': (0, 0), 'max': (1024, 1024)}
138
+ zoom_levels = [1.0, 2.0, 4.0, 8.0]
139
+
140
+ for zoom in zoom_levels:
141
+ print(f"\nGenerating texture tiles at zoom {zoom}x...")
142
+
143
+ terrain_tiles = texture_system.request_texture_tiles(
144
+ "terrain", viewport, zoom
145
+ )
146
+
147
+ print(f"Generated {len(terrain_tiles)} terrain tiles")
148
+
149
+ # Здесь можно визуализировать или сохранить тайлы
150
+ for (tx, ty, lod), tile in list(terrain_tiles.items())[:3]:
151
+ print(f" Tile ({tx},{ty}) LOD {lod}: {tile.shape}")
152
+
153
+ if terrain_tiles:
154
+ first_tile = next(iter(terrain_tiles.values()))
155
+ save_ppm(first_tile, EXAMPLES_DIR / "output" / f"game_texture_tile_{zoom:.1f}x.ppm")
156
+
157
+ return texture_system
158
+
159
+ if __name__ == "__main__":
160
+ parser = argparse.ArgumentParser(description="Game texture example")
161
+ add_interactive_args(parser)
162
+ args = parser.parse_args()
163
+
164
+ # Запускаем демо
165
+ system = demo_terrain_texture()
166
+ print("\nTexture system ready!")
167
+
168
+ if args.interactive:
169
+ config = InteractiveConfig.from_args(args, title="Game Texture (interactive)")
170
+ terrain_params = FractalParams(seed=42, base_scale=0.005, detail_level=3.0)
171
+ generator = FractalGenerator(terrain_params)
172
+ texture = InfiniteTexture(generator, "stone")
173
+
174
+ def render_frame(t, w, h):
175
+ zoom = 1.0 + 0.5 * (1 + np.sin(t * 0.2))
176
+ return texture.generate_tile(0, 0, w, h, zoom=zoom)[..., :3]
177
+
178
+ run_interactive(render_frame, config)
@@ -0,0 +1,102 @@
1
+ import sys
2
+ from pathlib import Path
3
+ import numpy as np
4
+ import argparse
5
+
6
+ ROOT = Path(__file__).resolve().parents[2]
7
+ if str(ROOT) not in sys.path:
8
+ sys.path.insert(0, str(ROOT))
9
+ EXAMPLES_DIR = Path(__file__).resolve().parent
10
+ if str(EXAMPLES_DIR) not in sys.path:
11
+ sys.path.insert(0, str(EXAMPLES_DIR))
12
+
13
+ from fractex.simplex_noise import SimplexTextureGenerator
14
+ from fractex.texture_blending import TextureBlender
15
+ from _output import save_ppm
16
+ from fractex.interactive import add_interactive_args, InteractiveConfig, run_interactive
17
+
18
+
19
+ def main():
20
+ parser = argparse.ArgumentParser(description="Texture blending example")
21
+ add_interactive_args(parser)
22
+ args = parser.parse_args()
23
+ # Генерация текстур
24
+ tex_gen = SimplexTextureGenerator(seed=42)
25
+ size = 256
26
+ clouds = tex_gen.generate_clouds(size, size)
27
+ rock = tex_gen.generate_marble(size, size)
28
+ grass = tex_gen.generate_grass(size, size)
29
+
30
+ height_map = rock[..., 0]
31
+ cloud_mask = clouds[..., 3]
32
+
33
+ blender = TextureBlender()
34
+ result = blender.blend_layer_stack(
35
+ base_texture=rock,
36
+ layers=[
37
+ {
38
+ "texture": grass,
39
+ "blend_mode": "overlay",
40
+ "opacity": 0.7,
41
+ "mask_params": {
42
+ "mask_type": "height_based",
43
+ "parameters": {
44
+ "height_map": height_map,
45
+ "min_height": 0.3,
46
+ "max_height": 0.6,
47
+ },
48
+ },
49
+ },
50
+ {
51
+ "texture": clouds,
52
+ "blend_mode": "screen",
53
+ "opacity": 0.3,
54
+ "mask": cloud_mask,
55
+ },
56
+ ],
57
+ )
58
+
59
+ print("Blended texture:", result.shape, result.min(), result.max())
60
+ save_ppm(result, EXAMPLES_DIR / "output" / "integration_blend.ppm")
61
+
62
+ if args.interactive:
63
+ config = InteractiveConfig.from_args(args, title="Integration Blend (interactive)")
64
+
65
+ def render_frame(t, w, h):
66
+ tex_gen = SimplexTextureGenerator(seed=42)
67
+ clouds = tex_gen.generate_clouds(w, h, scale=0.01 + 0.003 * np.sin(t * 0.1))
68
+ rock = tex_gen.generate_marble(w, h, scale=0.005 + 0.002 * np.cos(t * 0.07))
69
+ grass = tex_gen.generate_grass(w, h, scale=0.02 + 0.01 * np.sin(t * 0.08))
70
+ height_map = rock[..., 0]
71
+ cloud_mask = clouds[..., 3]
72
+ blender = TextureBlender()
73
+ return blender.blend_layer_stack(
74
+ base_texture=rock,
75
+ layers=[
76
+ {
77
+ "texture": grass,
78
+ "blend_mode": "overlay",
79
+ "opacity": 0.7,
80
+ "mask_params": {
81
+ "mask_type": "height_based",
82
+ "parameters": {
83
+ "height_map": height_map,
84
+ "min_height": 0.3,
85
+ "max_height": 0.6,
86
+ },
87
+ },
88
+ },
89
+ {
90
+ "texture": clouds,
91
+ "blend_mode": "screen",
92
+ "opacity": 0.3,
93
+ "mask": cloud_mask,
94
+ },
95
+ ],
96
+ )[..., :3]
97
+
98
+ run_interactive(render_frame, config)
99
+
100
+
101
+ if __name__ == "__main__":
102
+ main()
@@ -0,0 +1,70 @@
1
+ # Геометрические паттерны для разрушаемых материалов
2
+ import sys
3
+ from pathlib import Path
4
+ import numpy as np
5
+
6
+ ROOT = Path(__file__).resolve().parents[2]
7
+ if str(ROOT) not in sys.path:
8
+ sys.path.insert(0, str(ROOT))
9
+
10
+ from fractex.geometric_patterns_3d import GeometricPatternGenerator3D, GeometricPattern3D, PatternParameters
11
+
12
+
13
+ class DestructibleMaterial:
14
+ def __init__(self, pattern_type, params):
15
+ self.pattern_generator = GeometricPatternGenerator3D()
16
+ self.base_pattern = self.pattern_generator.generate_pattern(
17
+ pattern_type, (32, 32, 32), params
18
+ )
19
+ self.stress_field = np.zeros((32, 32, 32), dtype=np.float32)
20
+
21
+ def apply_stress(self, position, force):
22
+ stress_point = self.world_to_voxel(position)
23
+ self.propagate_stress(stress_point, force)
24
+ fracture_points = self.find_fracture_points()
25
+ self.modify_pattern_for_fractures(fracture_points)
26
+
27
+ def propagate_stress(self, point, force, depth: int = 0):
28
+ if depth > 6 or force < 0.5:
29
+ return
30
+ for direction in self.get_structure_directions(point):
31
+ self.stress_field[tuple(point)] += force
32
+ next_point = point + direction
33
+ if self.is_connected(point, next_point):
34
+ self.propagate_stress(next_point, force * 0.7, depth + 1)
35
+
36
+ def world_to_voxel(self, position):
37
+ pos = np.clip(np.array(position, dtype=int), 0, 31)
38
+ return pos
39
+
40
+ def get_structure_directions(self, point):
41
+ return [
42
+ np.array([1, 0, 0]),
43
+ np.array([-1, 0, 0]),
44
+ np.array([0, 1, 0]),
45
+ np.array([0, -1, 0]),
46
+ np.array([0, 0, 1]),
47
+ np.array([0, 0, -1]),
48
+ ]
49
+
50
+ def is_connected(self, point_a, point_b):
51
+ if np.any(point_b < 0) or np.any(point_b >= 32):
52
+ return False
53
+ return True
54
+
55
+ def find_fracture_points(self):
56
+ threshold = self.stress_field.mean() + self.stress_field.std()
57
+ return np.argwhere(self.stress_field > threshold)
58
+
59
+ def modify_pattern_for_fractures(self, fracture_points):
60
+ for p in fracture_points[:50]:
61
+ self.base_pattern[tuple(p)] = 0
62
+
63
+
64
+ if __name__ == "__main__":
65
+ material = DestructibleMaterial(
66
+ GeometricPattern3D.CRYSTAL_LATTICE,
67
+ PatternParameters(scale=1.0, thickness=0.05),
68
+ )
69
+ material.apply_stress((10, 10, 10), force=5.0)
70
+ print("Fracture points:", material.find_fracture_points().shape[0])