Added water splash simulator, made settings saving work for all simulators, moved all the simulators into their respective directories, improved the README, fixed too much logging

This commit is contained in:
csd4ni3l
2025-09-14 22:27:54 +02:00
parent 9c632d1bec
commit c010c41ccd
12 changed files with 395 additions and 54 deletions

View File

@@ -0,0 +1,146 @@
import arcade, arcade.gui, pyglet.gl, array, random, os, json
from utils.constants import WATER_ROWS, WATER_COLS
from game.water_simulator.shader import create_shader
class Game(arcade.gui.UIView):
def __init__(self, pypresence_client):
super().__init__()
self.pypresence_client = pypresence_client
self.pypresence_client.update(state="Playing a simulator", details="Water Simulator")
self.anchor = self.add_widget(arcade.gui.UIAnchorLayout(size_hint=(1, 1)))
self.settings_box = self.anchor.add(arcade.gui.UIBoxLayout(align="center", size_hint=(0.2, 1)).with_background(color=arcade.color.GRAY), anchor_x="right", anchor_y="bottom")
self.settings_label = self.settings_box.add(arcade.gui.UILabel(text="Settings", font_size=24))
if os.path.exists("data.json"):
with open("data.json", "r") as file:
self.settings = json.load(file)
else:
self.settings = {}
if not "water_simulator" in self.settings:
self.settings["water_simulator"] = {
"splash_strength": 0.1,
"splash_radius": 3,
"wave_speed": 1,
"damping": 0.02
}
self.splash_row = 0
self.splash_col = 0
self.current_splash_strength = 0
self.splash_strength = self.settings["water_simulator"].get("splash_strength", 0.1)
self.splash_radius = self.settings["water_simulator"].get("splash_radius", 3)
self.wave_speed = self.settings["water_simulator"].get("wave_speed", 1)
self.damping = self.settings["water_simulator"].get("damping", 0.02)
def on_show_view(self):
super().on_show_view()
self.settings_box.add(arcade.gui.UISpace(height=self.window.height / 75))
self.add_setting("Splash Strength: {value}", 0.1, 2.0, 0.1, "splash_strength")
self.add_setting("Splash Radius: {value}", 0.5, 10, 0.5, "splash_radius")
self.settings_box.add(arcade.gui.UISpace(height=self.window.height / 50))
self.advanced_label = self.settings_box.add(arcade.gui.UILabel("Advanced Settings", font_size=18, multiline=True))
self.settings_box.add(arcade.gui.UISpace(height=self.window.height / 75))
self.add_setting("Wave Speed: {value}", 0.1, 1.25, 0.05, "wave_speed")
self.add_setting("Damping: {value}", 0.005, 0.05, 0.001, "damping")
self.setup_game()
def on_update(self, delta_time):
with self.shader_program:
self.shader_program["rows"] = WATER_ROWS
self.shader_program["cols"] = WATER_COLS
self.shader_program["splash_row"] = self.splash_row
self.shader_program["splash_col"] = self.splash_col
self.shader_program["splash_strength"] = self.current_splash_strength
self.shader_program["splash_radius"] = self.splash_radius
self.shader_program["wave_speed"] = self.wave_speed
self.shader_program["damping"] = self.damping
self.shader_program.dispatch(self.water_image.width, self.water_image.height, 1, barrier=pyglet.gl.GL_ALL_BARRIER_BITS)
self.current_splash_strength = 0
def save_data(self):
self.settings.update({
"water_simulator": {
"splash_strength": self.splash_strength,
"splash_radius": self.splash_radius,
"wave_speed": self.wave_speed,
"damping": self.damping
}
})
with open("data.json", "w") as file:
file.write(json.dumps(self.settings, indent=4))
def setup_game(self):
self.shader_program, self.water_image, self.previous_heights_ssbo, self.current_heights_ssbo = create_shader()
self.image_sprite = pyglet.sprite.Sprite(img=self.water_image)
scale_x = (self.window.width * 0.8) / self.image_sprite.width
scale_y = self.window.height / self.image_sprite.height
self.image_sprite.scale_x = scale_x
self.image_sprite.scale_y = scale_y
grid = array.array('f', [random.uniform(-0.01, 0.01) for _ in range(WATER_ROWS * WATER_COLS)])
self.previous_heights_ssbo.set_data(grid.tobytes())
self.current_heights_ssbo.set_data(grid.tobytes())
def add_setting(self, text, min_value, max_value, step, local_variable, on_change=None):
label = self.settings_box.add(arcade.gui.UILabel(text.format(value=getattr(self, local_variable))))
slider = self.settings_box.add(arcade.gui.UISlider(value=getattr(self, local_variable), min_value=min_value, max_value=max_value, step=step))
slider._render_steps = lambda surface: None
if on_change:
slider.on_change = lambda event, label=label: on_change(label, event.new_value)
else:
slider.on_change = lambda event, label=label: self.change_value(label, text, local_variable, event.new_value)
def change_value(self, label, text, local_variable, value):
label.text = text.format(value=value)
self.settings["water_simulator"][local_variable] = value
setattr(self, local_variable, value)
def main_exit(self):
self.shader_program.delete()
self.previous_heights_ssbo.delete()
self.current_heights_ssbo.delete()
def on_key_press(self, symbol, modifiers):
if symbol == arcade.key.ESCAPE:
self.save_data()
from menus.main import Main
self.window.show_view(Main(self.pypresence_client))
def on_mouse_press(self, x, y, button, modifiers):
col = int(x / (self.window.width * 0.8) * WATER_COLS)
row = int(y / self.window.height * WATER_ROWS)
self.splash_row = row
self.splash_col = col
self.current_splash_strength = self.splash_strength
def on_draw(self):
super().on_draw()
self.image_sprite.draw()

View File

@@ -0,0 +1,80 @@
import pyglet, pyglet.graphics
from pyglet.gl import glBindBufferBase, GL_SHADER_STORAGE_BUFFER, GL_NEAREST
from utils.constants import WATER_ROWS, WATER_COLS
shader_source = f"""#version 430 core
layout(std430, binding = 3) buffer PreviousHeights {{
float previous_heights[{WATER_ROWS * WATER_COLS}];
}};
layout(std430, binding = 4) buffer CurrentHeights {{
float current_heights[{WATER_ROWS * WATER_COLS}];
}};
uniform int rows;
uniform int cols;
uniform int splash_row;
uniform int splash_col;
uniform float damping;
uniform float wave_speed;
uniform float splash_strength;
uniform float splash_radius;
layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout (location = 0, rgba32f) uniform image2D img_output;
void main() {{
ivec2 texel_coord = ivec2(gl_GlobalInvocationID.xy);
int row = texel_coord.y * rows / imageSize(img_output).y;
int col = texel_coord.x * cols / imageSize(img_output).x;
int current_index = (row * cols) + col;
if(row == 0 || col == 0 || row == rows-1 || col == cols-1) return;
float dist = distance(vec2(row, col), vec2(splash_row, splash_col));
if(dist <= splash_radius) current_heights[current_index] += splash_strength * (1.0 - dist / splash_radius);
float laplacian = current_heights[(row - 1) * cols + col] +
current_heights[(row + 1) * cols + col] +
current_heights[row * cols + (col - 1)] +
current_heights[row * cols + (col + 1)] -
4.0 * current_heights[current_index];
float dt = 0.1;
float h_new = 2.0 * current_heights[current_index]
- previous_heights[current_index] +
(wave_speed * wave_speed)*(dt*dt) * laplacian -
damping * (current_heights[current_index] - previous_heights[current_index]);
previous_heights[current_index] = current_heights[current_index];
current_heights[current_index] = h_new;
float minH = -0.5;
float maxH = 0.5;
float normH = clamp((h_new - minH) / (maxH - minH), 0.0, 1.0);
imageStore(img_output, texel_coord, vec4(0.0, 0.0, normH, 1.0));
}}
"""
def create_shader():
shader_program = pyglet.graphics.shader.ComputeShaderProgram(shader_source)
water_image = pyglet.image.Texture.create(WATER_COLS, WATER_ROWS, internalformat=pyglet.gl.GL_RGBA32F, min_filter=GL_NEAREST, mag_filter=GL_NEAREST)
uniform_location = shader_program['img_output']
water_image.bind_image_texture(unit=uniform_location)
previous_heights_ssbo = pyglet.graphics.BufferObject(WATER_COLS * WATER_ROWS * 4, usage=pyglet.gl.GL_DYNAMIC_COPY)
current_heights_ssbo = pyglet.graphics.BufferObject(WATER_COLS * WATER_ROWS * 4, usage=pyglet.gl.GL_DYNAMIC_COPY)
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, previous_heights_ssbo.id)
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, current_heights_ssbo.id)
return shader_program, water_image, previous_heights_ssbo, current_heights_ssbo