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,159 @@
1
+ import sys
2
+ from pathlib import Path
3
+ import numpy as np
4
+ import time
5
+ import itertools
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
+
12
+ from fractex import FractalParams, FractalGenerator, InfiniteTexture
13
+ from fractex.interactive import add_preset_arg, resolve_preset
14
+
15
+
16
+ def main():
17
+ try:
18
+ import matplotlib.pyplot as plt
19
+ from matplotlib.animation import FuncAnimation
20
+ import matplotlib as mpl
21
+ except Exception:
22
+ print("matplotlib is not available; cannot display splash image.")
23
+ return
24
+
25
+ parser = argparse.ArgumentParser(description="Fractex splash animation")
26
+ parser.add_argument("preset", nargs="?", help="Preset name")
27
+ add_preset_arg(parser, ["marble", "clouds", "wood", "lava", "water"], dest="preset_flag")
28
+ parser.add_argument("--scale", type=float, default=1.0, help="Render scale multiplier")
29
+ parser.add_argument("--fps", type=float, default=30.0, help="Target FPS")
30
+ args = parser.parse_args()
31
+
32
+ params = FractalParams(seed=7, base_scale=0.01, detail_level=2.0)
33
+ generator = FractalGenerator(params)
34
+ presets = ["marble", "clouds", "wood", "lava", "water"]
35
+ textures = {name: InfiniteTexture(generator, name) for name in presets}
36
+
37
+ selected = None
38
+ if args.preset_flag:
39
+ selected = resolve_preset(args.preset_flag.strip().lower(), presets)
40
+ elif args.preset:
41
+ selected = resolve_preset(args.preset.strip().lower(), presets)
42
+
43
+ try:
44
+ import tkinter as tk
45
+ root = tk.Tk()
46
+ root.withdraw()
47
+ screen_width = root.winfo_screenwidth()
48
+ screen_height = root.winfo_screenheight()
49
+ root.destroy()
50
+ except Exception:
51
+ screen_width, screen_height = 1920, 1080
52
+
53
+ mpl.rcParams["toolbar"] = "None"
54
+ fig, ax = plt.subplots()
55
+ try:
56
+ fig.canvas.manager.toolbar.setVisible(False)
57
+ except Exception:
58
+ pass
59
+ dpi = fig.get_dpi()
60
+ fig.set_size_inches(screen_width / dpi, screen_height / dpi)
61
+ ax.axis("off")
62
+ ax.set_title(f"Fractex Splash ({selected or 'auto'})")
63
+
64
+ texture = textures[presets[0]]
65
+ width = max(64, int(screen_width * max(0.1, args.scale)))
66
+ height = max(64, int(screen_height * max(0.1, args.scale)))
67
+ image = texture.generate_tile(0, 0, width, height, zoom=1.0)
68
+ rgb = image[..., :3]
69
+ im = ax.imshow(rgb, animated=True, aspect="auto")
70
+ fig.subplots_adjust(left=0, right=1, top=1, bottom=0)
71
+
72
+ target_fps = max(1.0, args.fps)
73
+ target_ms = 1000.0 / target_fps
74
+ render_scale = 1.0
75
+ detail_level = params.detail_level
76
+ last_time = time.perf_counter()
77
+ ema_ms = target_ms
78
+ phases = {name: 0.0 for name in presets}
79
+ last_t_base = 0.0
80
+
81
+ def _upscale_nearest(image: np.ndarray, out_w: int, out_h: int) -> np.ndarray:
82
+ in_h, in_w, _ = image.shape
83
+ if in_w == out_w and in_h == out_h:
84
+ return image
85
+ scale_x = max(1, out_w // in_w)
86
+ scale_y = max(1, out_h // in_h)
87
+ up = np.repeat(np.repeat(image, scale_y, axis=0), scale_x, axis=1)
88
+ return up[:out_h, :out_w, :]
89
+
90
+ def update(frame):
91
+ nonlocal render_scale, detail_level, last_time, ema_ms, last_t_base
92
+ t_base = frame / 10.0
93
+ delta_t = t_base - last_t_base
94
+ last_t_base = t_base
95
+
96
+ if selected:
97
+ base_name = selected
98
+ detail_name = selected
99
+ else:
100
+ base_name = presets[(frame // 240) % len(presets)]
101
+ detail_name = presets[(frame // 240 + 1) % len(presets)]
102
+
103
+ phases[base_name] += delta_t
104
+ base_texture = textures[base_name]
105
+ detail_texture = textures[detail_name]
106
+ t = phases[base_name]
107
+ t2 = phases[detail_name]
108
+
109
+ base_zoom = 1.0 + t * 0.02
110
+ detail_zoom = 2.5 + t * 0.05
111
+
112
+ decay = np.exp(-0.01 * t)
113
+ x0 = np.sin(t * 0.12) * 5.0 * decay
114
+ y0 = np.cos(t * 0.10) * 5.0 * decay
115
+ x1 = np.sin(t2 * 0.22 + 1.2) * 2.0 * decay
116
+ y1 = np.cos(t2 * 0.18 + 0.7) * 2.0 * decay
117
+
118
+ now = time.perf_counter()
119
+ dt_ms = (now - last_time) * 1000.0
120
+ last_time = now
121
+ ema_ms = ema_ms * 0.9 + dt_ms * 0.1
122
+
123
+ if ema_ms > target_ms * 1.1:
124
+ render_scale = max(0.4, render_scale * 0.9)
125
+ detail_level = max(0.6, detail_level * 0.9)
126
+ elif ema_ms < target_ms * 0.9:
127
+ render_scale = min(1.0, render_scale * 1.05)
128
+ detail_level = min(3.0, detail_level * 1.05)
129
+
130
+ generator.params.detail_level = detail_level
131
+ render_w = max(64, int(width * render_scale))
132
+ render_h = max(64, int(height * render_scale))
133
+
134
+ base = base_texture.generate_tile(x0, y0, render_w, render_h, zoom=base_zoom)[..., :3]
135
+ detail = detail_texture.generate_tile(x1, y1, render_w, render_h, zoom=detail_zoom)[..., :3]
136
+
137
+ depth = 0.35 + 0.15 * np.sin(t * 0.2)
138
+ rgb_frame = np.clip(base * (1.0 - depth) + detail * depth, 0, 1)
139
+
140
+ tint = 0.6 + 0.4 * np.sin(t * 0.2)
141
+ rgb_frame = np.clip(rgb_frame * np.array([1.0, tint, 0.9]), 0, 1)
142
+ rgb_frame = _upscale_nearest(rgb_frame, width, height)
143
+ im.set_array(rgb_frame)
144
+ return (im,)
145
+
146
+ anim = FuncAnimation(
147
+ fig,
148
+ update,
149
+ frames=itertools.count(),
150
+ interval=0,
151
+ blit=True,
152
+ repeat=True,
153
+ cache_frame_data=False,
154
+ )
155
+ plt.show(block=True)
156
+
157
+
158
+ if __name__ == "__main__":
159
+ main()
@@ -0,0 +1,76 @@
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.texture_blending import TerrainTextureBlender
14
+ from fractex.simplex_noise import SimplexTextureGenerator
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="Terrain example")
21
+ add_interactive_args(parser)
22
+ args = parser.parse_args()
23
+ terrain_blender = TerrainTextureBlender(seed=42)
24
+ tex_gen = SimplexTextureGenerator(seed=42)
25
+
26
+ size = 256
27
+ terrain_base = tex_gen.generate_terrain(size, size)
28
+ height_map = terrain_base[..., 0]
29
+
30
+ grass_texture = tex_gen.generate_grass(size, size)
31
+ dirt_texture = tex_gen.generate_wood(size, size)
32
+ rock_texture = tex_gen.generate_marble(size, size)
33
+ snow_texture = tex_gen.generate_clouds(size, size)
34
+
35
+ terrain = terrain_blender.create_terrain_material(
36
+ height_map=height_map,
37
+ texture_layers={
38
+ "grass": grass_texture,
39
+ "dirt": dirt_texture,
40
+ "rock": rock_texture,
41
+ "snow": snow_texture,
42
+ },
43
+ biome="mountain",
44
+ custom_params={
45
+ "height_ranges": [(0.0, 0.3), (0.2, 0.5), (0.4, 0.8), (0.7, 1.0)],
46
+ "slope_thresholds": [0.4, 0.6, 0.8],
47
+ },
48
+ )
49
+
50
+ grass_detail = tex_gen.generate_grass(size, size)
51
+ rock_detail = tex_gen.generate_marble(size, size)
52
+ moss_detail = tex_gen.generate_clouds(size, size)
53
+
54
+ terrain_detailed = terrain_blender.add_detail_layers(
55
+ terrain,
56
+ detail_textures=[grass_detail, rock_detail, moss_detail],
57
+ scale_factors=[2.0, 1.5, 3.0],
58
+ blend_modes=["overlay", "multiply", "screen"],
59
+ )
60
+
61
+ print("Terrain material:", terrain.shape, terrain.min(), terrain.max())
62
+ print("Terrain detailed:", terrain_detailed.shape)
63
+ save_ppm(terrain_detailed, EXAMPLES_DIR / "output" / "terrain_detailed.ppm")
64
+
65
+ if args.interactive:
66
+ config = InteractiveConfig.from_args(args, title="Terrain (interactive)")
67
+
68
+ def render_frame(t, w, h):
69
+ tex = SimplexTextureGenerator(seed=42)
70
+ return tex.generate_terrain(w, h, scale=0.004 + 0.002 * np.sin(t * 0.1))[..., :3]
71
+
72
+ run_interactive(render_frame, config)
73
+
74
+
75
+ if __name__ == "__main__":
76
+ main()
@@ -0,0 +1,94 @@
1
+ import sys
2
+ from pathlib import Path
3
+ import numpy as np
4
+ import argparse
5
+ from dataclasses import dataclass
6
+
7
+ ROOT = Path(__file__).resolve().parents[2]
8
+ if str(ROOT) not in sys.path:
9
+ sys.path.insert(0, str(ROOT))
10
+ EXAMPLES_DIR = Path(__file__).resolve().parent
11
+ if str(EXAMPLES_DIR) not in sys.path:
12
+ sys.path.insert(0, str(EXAMPLES_DIR))
13
+
14
+ from fractex.volume_scattering import UnderwaterScattering
15
+ from fractex.volume_textures import VolumeTextureGenerator3D
16
+ from _output import save_ppm, save_mp4
17
+ from fractex.interactive import add_interactive_args, InteractiveConfig, run_interactive
18
+
19
+
20
+ @dataclass
21
+ class Player:
22
+ position: np.ndarray
23
+ look_at: np.ndarray
24
+
25
+
26
+ def compute_bioluminescence(plankton, position, time_of_day: float) -> np.ndarray:
27
+ strength = 1.0 if time_of_day > 0.8 or time_of_day < 0.2 else 0.0
28
+ glow = plankton.data[..., 3].mean() if plankton.data is not None else 0.0
29
+ return np.ones((64, 96, 3), dtype=np.float32) * glow * strength
30
+
31
+
32
+ def main():
33
+ parser = argparse.ArgumentParser(description="Underwater example")
34
+ add_interactive_args(parser)
35
+ args = parser.parse_args()
36
+ underwater_renderer = UnderwaterScattering()
37
+
38
+ particle_generator = VolumeTextureGenerator3D(seed=42)
39
+ plankton = particle_generator.generate_clouds_3d(
40
+ width=32, height=32, depth=32,
41
+ scale=0.1, density=0.1, detail=2
42
+ )
43
+
44
+ player = Player(
45
+ position=np.array([0.0, -5.0, 0.0]),
46
+ look_at=np.array([0.0, -5.0, 1.0])
47
+ )
48
+ water_level = 0.0
49
+ time_of_day = 0.9
50
+ is_night = time_of_day > 0.8 or time_of_day < 0.2
51
+
52
+ frames = []
53
+ for t in [0.6, 0.7, 0.8, 0.85, 0.9, 0.95, 0.98, 0.75]:
54
+ is_night = t > 0.8 or t < 0.2
55
+ underwater_image = underwater_renderer.render_underwater(
56
+ camera_pos=player.position,
57
+ view_direction=player.look_at - player.position,
58
+ water_surface_height=water_level,
59
+ image_size=(192, 128),
60
+ max_depth=30.0
61
+ )
62
+ if is_night:
63
+ bioluminescence = compute_bioluminescence(
64
+ plankton, player.position, t
65
+ )
66
+ underwater_image = np.clip(underwater_image + bioluminescence * 0.3, 0, 1)
67
+ frames.append(underwater_image)
68
+
69
+ underwater_image = frames[-1]
70
+ print("Underwater image:", underwater_image.shape)
71
+ save_ppm(underwater_image, EXAMPLES_DIR / "output" / "underwater.ppm")
72
+ save_mp4(frames, EXAMPLES_DIR / "output" / "underwater.mp4", fps=8, stretch=True)
73
+
74
+ if args.interactive:
75
+ config = InteractiveConfig.from_args(args, title="Underwater (interactive)")
76
+
77
+ def render_frame(t, w, h):
78
+ underwater_image = underwater_renderer.render_underwater(
79
+ camera_pos=player.position,
80
+ view_direction=player.look_at - player.position,
81
+ water_surface_height=water_level,
82
+ image_size=(w, h),
83
+ max_depth=20.0
84
+ )
85
+ if t % 10 > 5:
86
+ bioluminescence = compute_bioluminescence(plankton, player.position, 0.9)
87
+ underwater_image = np.clip(underwater_image + bioluminescence * 0.3, 0, 1)
88
+ return underwater_image
89
+
90
+ run_interactive(render_frame, config)
91
+
92
+
93
+ if __name__ == "__main__":
94
+ main()
@@ -0,0 +1,112 @@
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.dynamic_textures_3d import DynamicTextureGenerator3D, DynamicTextureType, DynamicTextureState
14
+ from _output import save_ppm_sequence, save_mp4
15
+ from fractex.interactive import add_interactive_args, InteractiveConfig, run_interactive
16
+
17
+
18
+ def blend_lava_and_water(lava_state: DynamicTextureState, water_state: DynamicTextureState) -> DynamicTextureState:
19
+ blended = np.clip(lava_state.data * 0.6 + water_state.data * 0.4, 0, 1)
20
+ return DynamicTextureState(time=water_state.time, data=blended)
21
+
22
+
23
+ def add_bubbles_to_water(lava_state: DynamicTextureState, water_state: DynamicTextureState) -> DynamicTextureState:
24
+ if lava_state.temperature_field is None or water_state.data is None:
25
+ return water_state
26
+ hot_mask = lava_state.temperature_field > 800
27
+ bubbles = np.zeros_like(water_state.data[..., 0])
28
+ bubbles[hot_mask] = np.clip(lava_state.temperature_field[hot_mask] / 1200.0, 0, 1) * 0.1
29
+ water_state.data[..., 0] = np.clip(water_state.data[..., 0] + bubbles, 0, 1)
30
+ return water_state
31
+
32
+
33
+ def create_underwater_volcano():
34
+ """Создание подводного вулкана с лавой и пузырями"""
35
+ lava_generator = DynamicTextureGenerator3D(
36
+ dimensions=(32, 32, 32),
37
+ texture_type=DynamicTextureType.LAVA_FLOW,
38
+ seed=42
39
+ )
40
+
41
+ water_generator = DynamicTextureGenerator3D(
42
+ dimensions=(32, 32, 32),
43
+ texture_type=DynamicTextureType.WATER_FLOW,
44
+ seed=123
45
+ )
46
+
47
+ def safe_update(generator: DynamicTextureGenerator3D) -> DynamicTextureState:
48
+ try:
49
+ return generator.update()
50
+ except Exception as exc:
51
+ print(f"Simulation update skipped: {exc}")
52
+ depth, height, width = generator.dimensions
53
+ data = np.zeros((depth, height, width, 4), dtype=np.float32)
54
+ return DynamicTextureState(time=0.0, data=data)
55
+
56
+ states = []
57
+ for _ in range(12):
58
+ lava_state = safe_update(lava_generator)
59
+ water_state = safe_update(water_generator)
60
+ water_state = add_bubbles_to_water(lava_state, water_state)
61
+ blended_state = blend_lava_and_water(lava_state, water_state)
62
+ states.append(blended_state)
63
+
64
+ return states
65
+
66
+
67
+ if __name__ == "__main__":
68
+ parser = argparse.ArgumentParser(description="Underwater volcano example")
69
+ add_interactive_args(parser)
70
+ args = parser.parse_args()
71
+
72
+ states = create_underwater_volcano()
73
+ print("Frames:", len(states), "state shape:", states[-1].data.shape)
74
+ frames = []
75
+ for state in states:
76
+ mid = state.data.shape[0] // 2
77
+ frames.append(state.data[mid])
78
+ save_ppm_sequence(
79
+ frames,
80
+ EXAMPLES_DIR / "output" / "underwater_volkano_frames",
81
+ prefix="volkano",
82
+ stretch=True,
83
+ )
84
+ save_mp4(
85
+ frames,
86
+ EXAMPLES_DIR / "output" / "underwater_volkano.mp4",
87
+ fps=12,
88
+ stretch=True,
89
+ )
90
+
91
+ if args.interactive:
92
+ config = InteractiveConfig.from_args(args, title="Underwater Volcano (interactive)")
93
+ lava_generator = DynamicTextureGenerator3D(
94
+ dimensions=(32, 32, 32),
95
+ texture_type=DynamicTextureType.LAVA_FLOW,
96
+ seed=42
97
+ )
98
+ water_generator = DynamicTextureGenerator3D(
99
+ dimensions=(32, 32, 32),
100
+ texture_type=DynamicTextureType.WATER_FLOW,
101
+ seed=123
102
+ )
103
+
104
+ def render_frame(t, w, h):
105
+ lava_state = lava_generator.update()
106
+ water_state = water_generator.update()
107
+ water_state = add_bubbles_to_water(lava_state, water_state)
108
+ blended_state = blend_lava_and_water(lava_state, water_state)
109
+ mid = blended_state.data.shape[0] // 2
110
+ return blended_state.data[mid, :, :, :3]
111
+
112
+ run_interactive(render_frame, config)