change to template-based shaders, move fractal chooser to menus, add

burning ship and newton fractal, add escape radius to more fractals
This commit is contained in:
csd4ni3l
2025-05-27 21:55:27 +02:00
parent 564ce565a5
commit 22c46778d9
9 changed files with 453 additions and 146 deletions

View File

@@ -1,3 +1,3 @@
Fractal viewer in Python using compute shaders and the Arcade and Pyglet modules. Fractal viewer in Python using compute shaders and the Arcade and Pyglet modules.
Currently supports multiple types of Julia, the Sierpinsky Carpet and Mandelbrot. Currently supports Julia, multi-Julia, Mandelbrot, Multibrot, Burning Ship, Newton Fractal and the Sierpinsky Carpet.

95
game/burning_ship.py Normal file
View File

@@ -0,0 +1,95 @@
import arcade, arcade.gui, pyglet, json
from PIL import Image
from game.shader import create_iter_calc_shader
from utils.constants import menu_background_color, button_style, burning_ship_initial_real_min, burning_ship_initial_real_max, burning_ship_initial_imag_min, burning_ship_initial_imag_max
from utils.preload import button_texture, button_hovered_texture
class BurningShipViewer(arcade.gui.UIView):
def __init__(self, pypresence_client):
super().__init__()
self.pypresence_client = pypresence_client
self.real_min = burning_ship_initial_real_min
self.real_max = burning_ship_initial_real_max
self.imag_min = burning_ship_initial_imag_min
self.imag_max = burning_ship_initial_imag_max
with open("settings.json", "r") as file:
self.settings_dict = json.load(file)
self.max_iter = self.settings_dict.get("burning_ship_max_iter", 200)
self.zoom = 1.0
def on_show_view(self):
super().on_show_view()
self.shader_program, self.burning_ship_image = create_iter_calc_shader("burning_ship", self.window.width, self.window.height, self.settings_dict.get("burning_ship_precision", "Single").lower(), 2, int(self.settings_dict.get("burning_ship_escape_radius", 2)))
self.burning_ship_sprite = pyglet.sprite.Sprite(img=self.burning_ship_image)
self.create_image()
self.pypresence_client.update(state='Viewing Burning Ship', details=f'Zoom: {self.zoom}\nMax Iterations: {self.max_iter}', start=self.pypresence_client.start_time)
self.setup_ui()
def main_exit(self):
from menus.main import Main
self.window.show_view(Main(self.pypresence_client))
def setup_ui(self):
self.anchor = self.add_widget(arcade.gui.UIAnchorLayout(size_hint=(1, 1)))
self.info_box = self.anchor.add(arcade.gui.UIBoxLayout(space_between=10, vertical=False), anchor_x="center", anchor_y="top")
self.zoom_label = self.info_box.add(arcade.gui.UILabel(text=f"Zoom: {self.zoom}", font_name="Protest Strike", font_size=16))
self.max_iter_label = self.info_box.add(arcade.gui.UILabel(text=f"Max Iterations: {self.max_iter}", font_name="Protest Strike", font_size=16))
self.back_button = arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='<--', style=button_style, width=100, height=50)
self.back_button.on_click = lambda event: self.main_exit()
self.anchor.add(self.back_button, anchor_x="left", anchor_y="top", align_x=5, align_y=-5)
def zoom_at(self, center_x, center_y, zoom_factor):
center_real = self.real_min + (center_x / self.width) * (self.real_max - self.real_min)
center_imag = self.imag_min + (center_y / self.height) * (self.imag_max - self.imag_min)
new_real_range = (self.real_max - self.real_min) / zoom_factor
new_imag_range = (self.imag_max - self.imag_min) / zoom_factor
self.real_min = center_real - new_real_range / 2
self.real_max = center_real + new_real_range / 2
self.imag_min = center_imag - new_imag_range / 2
self.imag_max = center_imag + new_imag_range / 2
def create_image(self):
with self.shader_program:
self.shader_program['u_maxIter'] = int(self.max_iter)
self.shader_program['u_resolution'] = (self.window.width, self.window.height)
self.shader_program['u_real_range'] = (self.real_min, self.real_max)
self.shader_program['u_imag_range'] = (self.imag_min, self.imag_max)
self.shader_program.dispatch(self.burning_ship_image.width, self.burning_ship_image.height, 1, barrier=pyglet.gl.GL_ALL_BARRIER_BITS)
def on_mouse_press(self, x: int, y: int, button: int, modifiers: int) -> bool | None:
super().on_mouse_press(x, y, button, modifiers)
if button == arcade.MOUSE_BUTTON_LEFT:
zoom = self.settings_dict.get("burning_ship_zoom_increase", 2)
elif button == arcade.MOUSE_BUTTON_RIGHT:
zoom = 1 / self.settings_dict.get("burning_ship_zoom_increase", 2)
else:
return
self.zoom *= zoom
self.zoom_label.text = f"Zoom: {self.zoom}"
self.zoom_at(self.window.mouse.data["x"], self.window.mouse.data["y"], zoom)
self.create_image()
self.pypresence_client.update(state='Viewing Burning Ship', details=f'Zoom: {self.zoom}\nMax Iterations: {self.max_iter}', start=self.pypresence_client.start_time)
def on_draw(self):
self.window.clear()
self.burning_ship_sprite.draw()
self.ui.draw()

View File

@@ -2,7 +2,7 @@ import arcade, arcade.gui, pyglet, json
from PIL import Image from PIL import Image
from game.shader import create_julia_shader from game.shader import create_iter_calc_shader
from utils.constants import menu_background_color, button_style from utils.constants import menu_background_color, button_style
from utils.preload import button_texture, button_hovered_texture from utils.preload import button_texture, button_hovered_texture
@@ -37,7 +37,7 @@ class JuliaViewer(arcade.gui.UIView):
def on_show_view(self): def on_show_view(self):
super().on_show_view() super().on_show_view()
self.shader_program, self.julia_image = create_julia_shader(self.window.width, self.window.height, self.settings_dict.get("julia_precision", "Single").lower(), self.settings_dict.get("julia_escape_radius", 2), self.settings_dict.get("julia_type", "Classic swirling"), int(self.settings_dict.get("julia_n", 2))) self.shader_program, self.julia_image = create_iter_calc_shader("julia", self.window.width, self.window.height, self.settings_dict.get("julia_precision", "Single").lower(), int(self.settings_dict.get("julia_n", 2)), self.settings_dict.get("julia_escape_radius", 2), self.settings_dict.get("julia_type", "Classic swirling"))
self.julia_sprite = pyglet.sprite.Sprite(img=self.julia_image) self.julia_sprite = pyglet.sprite.Sprite(img=self.julia_image)

View File

@@ -2,7 +2,7 @@ import arcade, arcade.gui, pyglet, json
from PIL import Image from PIL import Image
from game.shader import create_mandelbrot_shader from game.shader import create_iter_calc_shader
from utils.constants import menu_background_color, button_style, mandelbrot_initial_real_min, mandelbrot_initial_real_max, mandelbrot_initial_imag_min, mandelbrot_initial_imag_max from utils.constants import menu_background_color, button_style, mandelbrot_initial_real_min, mandelbrot_initial_real_max, mandelbrot_initial_imag_min, mandelbrot_initial_imag_max
from utils.preload import button_texture, button_hovered_texture from utils.preload import button_texture, button_hovered_texture
@@ -25,7 +25,7 @@ class MandelbrotViewer(arcade.gui.UIView):
def on_show_view(self): def on_show_view(self):
super().on_show_view() super().on_show_view()
self.shader_program, self.mandelbrot_image = create_mandelbrot_shader(self.window.width, self.window.height, self.settings_dict.get("mandelbrot_precision", "Single").lower()) self.shader_program, self.mandelbrot_image = create_iter_calc_shader("mandelbrot", self.window.width, self.window.height, self.settings_dict.get("mandelbrot_precision", "Single").lower(), int(self.settings_dict.get("mandelbrot_n", 2)), int(self.settings_dict.get("mandelbrot_escape_radius", 2)))
self.mandelbrot_sprite = pyglet.sprite.Sprite(img=self.mandelbrot_image) self.mandelbrot_sprite = pyglet.sprite.Sprite(img=self.mandelbrot_image)

95
game/newton_fractal.py Normal file
View File

@@ -0,0 +1,95 @@
import arcade, arcade.gui, pyglet, json
from PIL import Image
from game.shader import create_iter_calc_shader
from utils.constants import menu_background_color, button_style, newton_fractal_initial_real_min, newton_fractal_initial_real_max, newton_fractal_initial_imag_min, newton_fractal_initial_imag_max
from utils.preload import button_texture, button_hovered_texture
class NewtonFractalViewer(arcade.gui.UIView):
def __init__(self, pypresence_client):
super().__init__()
self.pypresence_client = pypresence_client
self.real_min = newton_fractal_initial_real_min
self.real_max = newton_fractal_initial_real_max
self.imag_min = newton_fractal_initial_imag_min
self.imag_max = newton_fractal_initial_imag_max
with open("settings.json", "r") as file:
self.settings_dict = json.load(file)
self.max_iter = self.settings_dict.get("newton_fractal_max_iter", 200)
self.zoom = 1.0
def on_show_view(self):
super().on_show_view()
self.shader_program, self.newton_fractal_image = create_iter_calc_shader("newton_fractal", self.window.width, self.window.height, self.settings_dict.get("newton_fractal_precision", "Single").lower(), 2, int(self.settings_dict.get("newton_fractal_escape_radius", 2)))
self.newton_fractal_sprite = pyglet.sprite.Sprite(img=self.newton_fractal_image)
self.create_image()
self.pypresence_client.update(state='Viewing Newton Fractal', details=f'Zoom: {self.zoom}\nMax Iterations: {self.max_iter}', start=self.pypresence_client.start_time)
self.setup_ui()
def main_exit(self):
from menus.main import Main
self.window.show_view(Main(self.pypresence_client))
def setup_ui(self):
self.anchor = self.add_widget(arcade.gui.UIAnchorLayout(size_hint=(1, 1)))
self.info_box = self.anchor.add(arcade.gui.UIBoxLayout(space_between=10, vertical=False), anchor_x="center", anchor_y="top")
self.zoom_label = self.info_box.add(arcade.gui.UILabel(text=f"Zoom: {self.zoom}", font_name="Protest Strike", font_size=16))
self.max_iter_label = self.info_box.add(arcade.gui.UILabel(text=f"Max Iterations: {self.max_iter}", font_name="Protest Strike", font_size=16))
self.back_button = arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='<--', style=button_style, width=100, height=50)
self.back_button.on_click = lambda event: self.main_exit()
self.anchor.add(self.back_button, anchor_x="left", anchor_y="top", align_x=5, align_y=-5)
def zoom_at(self, center_x, center_y, zoom_factor):
center_real = self.real_min + (center_x / self.width) * (self.real_max - self.real_min)
center_imag = self.imag_min + (center_y / self.height) * (self.imag_max - self.imag_min)
new_real_range = (self.real_max - self.real_min) / zoom_factor
new_imag_range = (self.imag_max - self.imag_min) / zoom_factor
self.real_min = center_real - new_real_range / 2
self.real_max = center_real + new_real_range / 2
self.imag_min = center_imag - new_imag_range / 2
self.imag_max = center_imag + new_imag_range / 2
def create_image(self):
with self.shader_program:
self.shader_program['u_maxIter'] = int(self.max_iter)
self.shader_program['u_resolution'] = (self.window.width, self.window.height)
self.shader_program['u_real_range'] = (self.real_min, self.real_max)
self.shader_program['u_imag_range'] = (self.imag_min, self.imag_max)
self.shader_program.dispatch(self.newton_fractal_image.width, self.newton_fractal_image.height, 1, barrier=pyglet.gl.GL_ALL_BARRIER_BITS)
def on_mouse_press(self, x: int, y: int, button: int, modifiers: int) -> bool | None:
super().on_mouse_press(x, y, button, modifiers)
if button == arcade.MOUSE_BUTTON_LEFT:
zoom = self.settings_dict.get("newton_fractal_zoom_increase", 2)
elif button == arcade.MOUSE_BUTTON_RIGHT:
zoom = 1 / self.settings_dict.get("newton_fractal_zoom_increase", 2)
else:
return
self.zoom *= zoom
self.zoom_label.text = f"Zoom: {self.zoom}"
self.zoom_at(self.window.mouse.data["x"], self.window.mouse.data["y"], zoom)
self.create_image()
self.pypresence_client.update(state='Viewing Newton Fractal', details=f'Zoom: {self.zoom}\nMax Iterations: {self.max_iter}', start=self.pypresence_client.start_time)
def on_draw(self):
self.window.clear()
self.newton_fractal_sprite.draw()
self.ui.draw()

View File

@@ -1,156 +1,245 @@
import pyglet import pyglet
from utils.constants import c_for_julia_type from utils.constants import c_for_julia_type
mandelbrot_compute_source = """#version 430 core newton_coloring = """vec4 getColor(int color_number) {{
vec4 value = vec4(0.0, 0.0, 0.0, 1.0);
if (color_number == 0) {{
value.r = 1.0;
}}
else if (color_number == 1) {{
value.g = 1.0;
}}
else if (color_number == 2) {{
value.b = 1.0;
}}
return value;
}}
"""
polynomial_coloring = """vec4 getColor(int iters) {{
vec4 value = vec4(0.0, 0.0, 0.0, 1.0);
if (iters != u_maxIter) {{
float t = float(iters) / float(u_maxIter);
value.r = 9.0 * (1.0 - t) * t * t * t;
value.g = 15.0 * (1.0 - t) * (1.0 - t) * t * t;
value.b = 8.5 * (1.0 - t) * (1.0 - t) * (1.0 - t) * t;
}}
return value;
}}
"""
fire_coloring = """vec4 getColor(int iters) {{
vec4 value = vec4(0.0, 0.0, 0.0, 1.0);
if (iters != u_maxIter) {{
float t = float(iters) / float(u_maxIter);
value.r = 3.0 * t;
value.g = 2.0 * t * t;
value.b = t * t * t;
}}
return value;
}}
"""
iter_fractal_template = """#version 430 core
uniform int u_maxIter; uniform int u_maxIter;
uniform vec2 u_resolution; uniform vec2 u_resolution;
uniform vec2 u_real_range; uniform vec2 u_real_range;
uniform vec2 u_imag_range; uniform vec2 u_imag_range;
layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in; layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(location = 0, rgba32f) uniform image2D img_output; layout(location = 0, rgba32f) uniform image2D img_output;
int mandelbrot({vec2type} c, int maxIter) { {coloring_func}
{vec2type} z = {vec2type}(0.0, 0.0); {iter_calc_func}
for (int n = 0; n < maxIter; n++) {
if (dot(z, z) > 4.0) {
return n;
}
z = {vec2type}(
z.x * z.x - z.y * z.y + c.x,
2.0 * z.x * z.y + c.y
);
}
return maxIter;
}
{vec2type} map_pixel({floattype} x, {floattype} y, {vec2type} resolution, {vec2type} real_range, {vec2type} imag_range) { {vec2type} map_pixel({floattype} x, {floattype} y, {vec2type} resolution, {vec2type} real_range, {vec2type} imag_range) {{
{floattype} real = real_range.x + (x / resolution.x) * (real_range.y - real_range.x); {floattype} real = real_range.x + (x / resolution.x) * (real_range.y - real_range.x);
{floattype} imag = imag_range.x + (y / resolution.y) * (imag_range.y - imag_range.x); {floattype} imag = imag_range.x + (y / resolution.y) * (imag_range.y - imag_range.x);
return {vec2type}(real, imag); return {vec2type}(real, imag);
} }}
void main() { void main() {{
ivec2 texel_coord = ivec2(gl_GlobalInvocationID.xy); ivec2 texel_coord = ivec2(gl_GlobalInvocationID.xy);
{vec2type} c = map_pixel({floattype}(texel_coord.x), {floattype}(texel_coord.y), u_resolution, u_real_range, u_imag_range); {vec2type} pos = map_pixel({floattype}(texel_coord.x), {floattype}(texel_coord.y), u_resolution, u_real_range, u_imag_range);
int iters = mandelbrot(c, u_maxIter); int iters = calculate_iters(pos);
vec4 value = getColor(iters);
vec4 value = vec4(0.0, 0.0, 0.0, 1.0);
if (iters != u_maxIter) {
float t = float(iters) / float(u_maxIter);
value.r = 9.0 * (1.0 - t) * t * t * t;
value.g = 15.0 * (1.0 - t) * (1.0 - t) * t * t;
value.b = 8.5 * (1.0 - t) * (1.0 - t) * (1.0 - t) * t;
}
imageStore(img_output, texel_coord, value); imageStore(img_output, texel_coord, value);
} }}
""" """
sierpinsky_carpet_compute_source = """#version 430 core sierpinsky_carpet_compute_source = """#version 430 core
uniform int u_depth; uniform int u_depth;
uniform int u_zoom; uniform int u_zoom;
uniform vec2 u_center; uniform vec2 u_center;
layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in; layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(location = 0, rgba32f) uniform image2D img_output; layout(location = 0, rgba32f) uniform image2D img_output;
void main() {{
void main() {
{vec2type} centered = {vec2type}(gl_GlobalInvocationID.xy) - u_center; {vec2type} centered = {vec2type}(gl_GlobalInvocationID.xy) - u_center;
{vec2type} zoomed = centered / u_zoom; {vec2type} zoomed = centered / u_zoom;
{vec2type} final_coord = zoomed + u_center; {vec2type} final_coord = zoomed + u_center;
ivec2 coord = ivec2(final_coord); ivec2 coord = ivec2(final_coord);
bool isHole = false; bool isHole = false;
for (int i = 0; i < u_depth; ++i) {{
for (int i = 0; i < u_depth; ++i) { if (coord.x % 3 == 1 && coord.y % 3 == 1) {{
if (coord.x % 3 == 1 && coord.y % 3 == 1) {
isHole = true; isHole = true;
break; break;
} }}
coord /= 3; coord /= 3;
} }}
vec4 color = isHole ? vec4(0, 0, 0, 1) : vec4(1, 1, 1, 1); vec4 color = isHole ? vec4(0, 0, 0, 1) : vec4(1, 1, 1, 1);
imageStore(img_output, ivec2(gl_GlobalInvocationID.xy), color); imageStore(img_output, ivec2(gl_GlobalInvocationID.xy), color);
} }}
""" """
julia_template = """#version 430 core
uniform int u_maxIter;
uniform vec2 u_resolution;
uniform vec2 u_real_range;
uniform vec2 u_imag_range;
layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(location = 0, rgba32f) uniform image2D img_output;
{vec2type} map_pixel({floattype} x, {floattype} y, {vec2type} resolution, {vec2type} real_range, {vec2type} imag_range) {
{floattype} real = real_range.x + (x / resolution.x) * (real_range.y - real_range.x);
{floattype} imag = imag_range.x + (y / resolution.y) * (imag_range.y - imag_range.x);
return {vec2type}(real, imag);
}
void main() {
ivec2 texel_coord = ivec2(gl_GlobalInvocationID.xy);
float R = {escape_radius};
int n = {julia_n};
{vec2type} c = {vec2type}{julia_c};
{vec2type} z = map_pixel({floattype}(texel_coord.x), {floattype}(texel_coord.y), u_resolution, u_real_range, u_imag_range);
normal_julia_calc = """int calculate_iters({vec2type} z) {{
int iters = 0; int iters = 0;
float R = {escape_radius};
{julia_calc} int n = {multi_n};
{vec2type} c = {vec2type}{julia_c};
vec4 value = vec4(0.0, 0.0, 0.0, 1.0); while (dot(z, z) < R * R && iters < u_maxIter){{
if (iters != u_maxIter) {
float t = float(iters) / float(u_maxIter);
value.r = 9.0 * (1.0 - t) * t * t * t;
value.g = 15.0 * (1.0 - t) * (1.0 - t) * t * t;
value.b = 8.5 * (1.0 - t) * (1.0 - t) * (1.0 - t) * t;
}
imageStore(img_output, texel_coord, value);
}
"""
normal_julia_calc = """while (z.x * z.x + z.y * z.y < R*R && iters < u_maxIter)
{
{floattype} xtemp = z.x * z.x - z.y * z.y; {floattype} xtemp = z.x * z.x - z.y * z.y;
z.y = 2 * z.x * z.y + c.y; z.y = 2 * z.x * z.y + c.y;
z.x = xtemp + c.x; z.x = xtemp + c.x;
iters++;
iters = iters + 1; }}
} return iters;
}}
""" """
multi_julia_calc = """while ((z.x * z.x + z.y * z.y) < R*R && iters < u_maxIter) { multi_julia_calc = """int calculate_iters(float z) {{
{floattype} xtmp = pow((z.x * z.x + z.y * z.y), (n / 2)) * cos(n * atan(z.y, z.x)) + c.x; int iters = 0;
z.y = pow((z.x * z.x + z.y * z.y), (n / 2)) * sin(n * atan(z.y, z.x)) + c.y; float R = {escape_radius};
z.x = xtmp; float n = float({multi_n});
float c = float({julia_c});
iters = iters + 1; while (dot(z, z) < R * R && iters < u_maxIter) {{
} float r = length(z);
float theta = atan(z.y, z.x);
float r_pow = pow(r, n);
z = vec2(r_pow * cos(n * theta), r_pow * sin(n * theta)) + c;
iters++;
}}
return iters;
}}
"""
mandelbrot_calc = """int calculate_iters({vec2type} c) {{
int iters = 0;
{vec2type} z = {vec2type}(0.0, 0.0);
float R = {escape_radius};
while (dot(z, z) < R * R && iters < u_maxIter) {{
z = {vec2type}(
z.x * z.x - z.y * z.y + c.x,
2.0 * z.x * z.y + c.y
);
iters++;
}}
return iters;
}}
"""
multibrot_calc = """int calculate_iters(vec2 c) {{
int iters = 0;
vec2 z = vec2(0.0);
float n = {multi_n};
float R = {escape_radius};
while (dot(z, z) < R * R && iters < u_maxIter) {{
float r = length(z);
float theta = atan(z.y, z.x);
float r_n = pow(r, n);
float theta_n = n * theta;
z = r_n * vec2(cos(theta_n), sin(theta_n)) + c;
iters++;
}}
return iters;
}}
"""
burning_ship_calc = """int calculate_iters({vec2type} c) {{
int iters = 0;
{vec2type} z = {vec2type}(0.0, 0.0);
float R = {escape_radius};
while (dot(z, z) < R * R && iters < u_maxIter) {{
{floattype} xtemp = z.x * z.x - z.y * z.y + c.x;
z.y = abs(2.0 * z.x * z.y) + c.y;
z.x = xtemp;
iters++;
}}
return iters;
}}
"""
newton_fractal_calc = """vec2 cmul(vec2 a, vec2 b) {{
return vec2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);
}}
vec2 cdiv(vec2 a, vec2 b) {{
float denom = b.x * b.x + b.y * b.y;
return vec2((a.x * b.x + a.y * b.y) / denom, (a.y * b.x - a.x * b.y) / denom);
}}
vec2 cpow(vec2 z, int power) {{
vec2 result = vec2(1.0, 0.0);
for (int i = 0; i < power; ++i) {{
result = cmul(result, z);
}}
return result;
}}
vec2 func(vec2 z) {{
return cpow(z, 3) - vec2(1.0, 0.0);
}}
vec2 derivative(vec2 z) {{
return 3.0 * cmul(z, z);
}}
int calculate_iters(vec2 z) {{
float tolerance = 0.000001;
vec2 roots[3] = vec2[](
vec2(1, 0),
vec2(-0.5, 0.866025404),
vec2(-0.5, -0.866025404)
);
for (int iters = 0; iters < u_maxIter; iters++) {{
z -= cdiv(func(z), derivative(z));
for (int i = 0; i < 3; i++) {{
vec2 difference = z - roots[i];
if (abs(difference.x) < tolerance && abs(difference.y) < tolerance) {{
return i;
}}
}}
}}
return -1;
}}
""" """
def create_sierpinsky_carpet_shader(width, height, precision="single"): def create_sierpinsky_carpet_shader(width, height, precision="single"):
shader_source = sierpinsky_carpet_compute_source shader_source = sierpinsky_carpet_compute_source
if precision == "single": replacements = {
shader_source = shader_source.replace("{vec2type}", "vec2").replace("{floattype}", "float") "vec2type": "dvec2" if precision == "double" else "vec2",
elif precision == "double": "floattype": "double" if precision == "double" else "float"
shader_source = shader_source.replace("{vec2type}", "dvec2").replace("{floattype}", "double") }
else:
raise TypeError("Invalid Precision") shader_source = shader_source.format_map(replacements)
shader_program = pyglet.graphics.shader.ComputeShaderProgram(shader_source) shader_program = pyglet.graphics.shader.ComputeShaderProgram(shader_source)
@@ -161,27 +250,40 @@ def create_sierpinsky_carpet_shader(width, height, precision="single"):
return shader_program, sierpinsky_carpet_image return shader_program, sierpinsky_carpet_image
def create_julia_shader(width, height, precision="single", escape_radius=2, julia_type="Classic swirling", julia_n=2): def create_iter_calc_shader(fractal_type, width, height, precision="single", multi_n=2, escape_radius=2, julia_type="Classic swirling"):
shader_source = julia_template shader_source = iter_fractal_template
if julia_n == 2: replacements = {
shader_source = shader_source.replace("{julia_calc}", normal_julia_calc) "multi_n": str(multi_n),
"julia_c": str(c_for_julia_type[julia_type]),
"escape_radius": str(escape_radius),
"vec2type": "dvec2" if int(multi_n) == 2 and precision == "double" else "vec2",
"floattype": "double" if int(multi_n) == 2 and precision == "double" else "float"
}
if precision == "single": replacements["coloring_func"] = polynomial_coloring.format_map(replacements)
shader_source = shader_source.replace("{vec2type}", "vec2").replace("{floattype}", "float")
elif precision == "double":
shader_source = shader_source.replace("{vec2type}", "dvec2").replace("{floattype}", "double")
if fractal_type == "mandelbrot":
if int(multi_n) == 2:
replacements["iter_calc_func"] = mandelbrot_calc.format_map(replacements)
else: else:
shader_source = shader_source.replace("{julia_calc}", multi_julia_calc) replacements["iter_calc_func"] = multibrot_calc.format_map(replacements)
shader_source = shader_source.replace("{vec2type}", "vec2").replace("{floattype}", "float") # pow and atan only support floats
shader_source = shader_source.replace("{julia_n}", str(julia_n)) elif fractal_type == "julia":
if int(multi_n) == 2:
replacements["iter_calc_func"] = normal_julia_calc.format_map(replacements)
else:
replacements["iter_calc_func"] = multi_julia_calc.format_map(replacements)
julia_c = c_for_julia_type[julia_type] elif fractal_type == "burning_ship":
shader_source = shader_source.replace("{julia_c}", str(julia_c)) replacements["coloring_func"] = fire_coloring.format_map(replacements)
replacements["iter_calc_func"] = burning_ship_calc.format_map(replacements)
shader_source = shader_source.replace("{escape_radius}", str(escape_radius)) elif fractal_type == "newton_fractal":
replacements["coloring_func"] = newton_coloring.format_map(replacements)
replacements["iter_calc_func"] = newton_fractal_calc.format_map(replacements)
shader_source = shader_source.format_map(replacements)
shader_program = pyglet.graphics.shader.ComputeShaderProgram(shader_source) shader_program = pyglet.graphics.shader.ComputeShaderProgram(shader_source)
@@ -191,23 +293,3 @@ def create_julia_shader(width, height, precision="single", escape_radius=2, juli
julia_image.bind_image_texture(unit=uniform_location) julia_image.bind_image_texture(unit=uniform_location)
return shader_program, julia_image return shader_program, julia_image
def create_mandelbrot_shader(width, height, precision="single"):
shader_source = mandelbrot_compute_source
if precision == "single":
shader_source = shader_source.replace("{vec2type}", "vec2").replace("{floattype}", "float")
elif precision == "double":
shader_source = shader_source.replace("{vec2type}", "dvec2").replace("{floattype}", "double")
else:
raise TypeError("Invalid Precision")
shader_program = pyglet.graphics.shader.ComputeShaderProgram(shader_source)
mandelbrot_image = pyglet.image.Texture.create(width, height, internalformat=pyglet.gl.GL_RGBA32F)
uniform_location = shader_program['img_output']
mandelbrot_image.bind_image_texture(unit=uniform_location)
return shader_program, mandelbrot_image

View File

@@ -32,6 +32,12 @@ class FractalChooser(arcade.gui.UIView):
self.julia_button = self.grid.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='Julia', style=button_style, width=200, height=200), row=0, column=2) self.julia_button = self.grid.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='Julia', style=button_style, width=200, height=200), row=0, column=2)
self.julia_button.on_click = lambda event: self.julia() self.julia_button.on_click = lambda event: self.julia()
self.burning_ship_button = self.grid.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='Burning Ship', style=button_style, width=200, height=200), row=1, column=0)
self.burning_ship_button.on_click = lambda event: self.burning_ship()
self.newton_fractal_button = self.grid.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='Newton Fractal', style=button_style, width=200, height=200), row=1, column=1)
self.newton_fractal_button.on_click = lambda event: self.newton_fractal()
def main_exit(self): def main_exit(self):
from menus.main import Main from menus.main import Main
self.window.show_view(Main(self.pypresence_client)) self.window.show_view(Main(self.pypresence_client))
@@ -47,3 +53,11 @@ class FractalChooser(arcade.gui.UIView):
def julia(self): def julia(self):
from game.julia import JuliaViewer from game.julia import JuliaViewer
self.window.show_view(JuliaViewer(self.pypresence_client)) self.window.show_view(JuliaViewer(self.pypresence_client))
def burning_ship(self):
from game.burning_ship import BurningShipViewer
self.window.show_view(BurningShipViewer(self.pypresence_client))
def newton_fractal(self):
from game.newton_fractal import NewtonFractalViewer
self.window.show_view(NewtonFractalViewer(self.pypresence_client))

View File

@@ -1,5 +1,4 @@
import arcade, arcade.gui, asyncio, pypresence, time, copy, json import arcade, arcade.gui, asyncio, pypresence, time, copy, json
from game.play import FractalChooser
from utils.preload import button_texture, button_hovered_texture from utils.preload import button_texture, button_hovered_texture
from utils.constants import big_button_style, discord_presence_id from utils.constants import big_button_style, discord_presence_id
from utils.utils import FakePyPresence from utils.utils import FakePyPresence
@@ -60,7 +59,7 @@ class Main(arcade.gui.UIView):
self.settings_button.on_click = lambda event: self.settings() self.settings_button.on_click = lambda event: self.settings()
def play(self): def play(self):
from game.play import FractalChooser from menus.fractal_chooser import FractalChooser
self.window.show_view(FractalChooser(self.pypresence_client)) self.window.show_view(FractalChooser(self.pypresence_client))
def settings(self): def settings(self):

View File

@@ -12,6 +12,16 @@ mandelbrot_initial_real_max = 1.0
mandelbrot_initial_imag_min = -1.0 mandelbrot_initial_imag_min = -1.0
mandelbrot_initial_imag_max = 1.0 mandelbrot_initial_imag_max = 1.0
burning_ship_initial_real_min = -2.0
burning_ship_initial_real_max = 1.5
burning_ship_initial_imag_min = -2.0
burning_ship_initial_imag_max = 1.0
newton_fractal_initial_real_min = -2.0
newton_fractal_initial_real_max = 2.0
newton_fractal_initial_imag_min = -2.0
newton_fractal_initial_imag_max = 2.0
c_for_julia_type = { c_for_julia_type = {
"Classic swirling": (-0.7, 0.27015), "Classic swirling": (-0.7, 0.27015),
"Douady rabbit": (-0.123, 0.745), "Douady rabbit": (-0.123, 0.745),
@@ -35,9 +45,17 @@ slider_style = {'normal': slider_default_style, 'hover': slider_hover_style, 'pr
settings = { settings = {
"Mandelbrot": { "Mandelbrot": {
"Float Precision": {"type": "option", "options": ["Single", "Double"], "config_key": "mandelbrot_precision", "default": "Single"}, "Float Precision": {"type": "option", "options": ["Single", "Double"], "config_key": "mandelbrot_precision", "default": "Single"},
"N": {"type": "slider", "min": 1, "max": 10, "config_key": "mandelbrot_n", "default": 2, "step": 1},
"Escape Radius": {"type": "slider", "min": 1, "max": 10, "config_key": "mandelbrot_escape_radius", "default": 2, "step": 0.1},
"Zoom Increase Per Click": {"type": "slider", "min": 2, "max": 100, "config_key": "mandelbrot_zoom_increase", "default": 2}, "Zoom Increase Per Click": {"type": "slider", "min": 2, "max": 100, "config_key": "mandelbrot_zoom_increase", "default": 2},
"Max Iterations": {"type": "slider", "min": 100, "max": 10000, "config_key": "mandelbrot_max_iter", "default": 200, "step": 100} "Max Iterations": {"type": "slider", "min": 100, "max": 10000, "config_key": "mandelbrot_max_iter", "default": 200, "step": 100}
}, },
"Burning Ship": {
"Float Precision": {"type": "option", "options": ["Single", "Double"], "config_key": "burning_ship_precision", "default": "Single"},
"Escape Radius": {"type": "slider", "min": 1, "max": 10, "config_key": "burning_ship_escape_radius", "default": 2, "step": 0.1},
"Zoom Increase Per Click": {"type": "slider", "min": 2, "max": 100, "config_key": "burning_ship_zoom_increase", "default": 2},
"Max Iterations": {"type": "slider", "min": 100, "max": 10000, "config_key": "burning_ship_max_iter", "default": 200, "step": 100}
},
"Sierpinsky Carpet": { "Sierpinsky Carpet": {
"Float Precision": {"type": "option", "options": ["Single", "Double"], "config_key": "sierpinsky_precision", "default": "Single"}, "Float Precision": {"type": "option", "options": ["Single", "Double"], "config_key": "sierpinsky_precision", "default": "Single"},
"Zoom Increase Per Click": {"type": "slider", "min": 2, "max": 100, "config_key": "sierpinsky_zoom_increase", "default": 2}, "Zoom Increase Per Click": {"type": "slider", "min": 2, "max": 100, "config_key": "sierpinsky_zoom_increase", "default": 2},
@@ -49,7 +67,11 @@ settings = {
"N": {"type": "slider", "min": 1, "max": 10, "config_key": "julia_n", "default": 2, "step": 1}, "N": {"type": "slider", "min": 1, "max": 10, "config_key": "julia_n", "default": 2, "step": 1},
"Escape Radius": {"type": "slider", "min": 1, "max": 10, "config_key": "julia_escape_radius", "default": 2, "step": 0.1}, "Escape Radius": {"type": "slider", "min": 1, "max": 10, "config_key": "julia_escape_radius", "default": 2, "step": 0.1},
"Zoom Increase Per Click": {"type": "slider", "min": 2, "max": 100, "config_key": "julia_zoom_increase", "default": 2}, "Zoom Increase Per Click": {"type": "slider", "min": 2, "max": 100, "config_key": "julia_zoom_increase", "default": 2},
"Max Iterations": {"type": "slider", "min": 100, "max": 10000, "config_key": "julia_max_iter", "default": 200, "step": 100} "Max Iterations": {"type": "slider", "min": 100, "max": 4000, "config_key": "julia_max_iter", "default": 200, "step": 100}
},
"Newton Fractal": {
"Zoom Increase Per Click": {"type": "slider", "min": 2, "max": 100, "config_key": "newton_fractal_zoom_increase", "default": 2},
"Max Iterations": {"type": "slider", "min": 100, "max": 10000, "config_key": "newton_fractal_max_iter", "default": 200, "step": 100}
}, },
"Graphics": { "Graphics": {
"Window Mode": {"type": "option", "options": ["Windowed", "Fullscreen", "Borderless"], "config_key": "window_mode", "default": "Windowed"}, "Window Mode": {"type": "option", "options": ["Windowed", "Fullscreen", "Borderless"], "config_key": "window_mode", "default": "Windowed"},