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
|
@@ -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)
|