diff --git a/CREDITS b/CREDITS index 52664d6..015b4a1 100644 --- a/CREDITS +++ b/CREDITS @@ -1,3 +1,5 @@ +The Roboto Black font used in this project is licensed under the Open Font License. Read assets/fonts/OFL.txt for more information. + Huge Thanks to Python for being the programming language used in this game. https://www.python.org/ diff --git a/README.md b/README.md index 2d9e24e..d299d23 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ Fractal viewer in Python using compute shaders and the Arcade and Pyglet modules. -Currently supports Julia, multi-Julia, Mandelbrot, Multibrot, Burning Ship, Newton Fractal and the Sierpinsky Carpet. +Currently supports Julia, multi-Julia, Mandelbrot, Multibrot, Mandelbar, multi-Mandelbar, Phoenix Fractal, Lambda Fractal, Burning Ship, Newton Fractal and the Sierpinsky Carpet. diff --git a/assets/fonts/OFL.txt b/assets/fonts/OFL.txt new file mode 100644 index 0000000..a417551 --- /dev/null +++ b/assets/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2011 The Roboto Project Authors (https://github.com/googlefonts/roboto-classic) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/fonts/ProtestStrike-Regular.ttf b/assets/fonts/ProtestStrike-Regular.ttf deleted file mode 100644 index 3a88f0c..0000000 Binary files a/assets/fonts/ProtestStrike-Regular.ttf and /dev/null differ diff --git a/assets/fonts/Roboto-Black.ttf b/assets/fonts/Roboto-Black.ttf new file mode 100644 index 0000000..d51221a Binary files /dev/null and b/assets/fonts/Roboto-Black.ttf differ diff --git a/game/iter_fractal_viewer.py b/game/iter_fractal_viewer.py index 768e347..1a41b00 100644 --- a/game/iter_fractal_viewer.py +++ b/game/iter_fractal_viewer.py @@ -48,8 +48,8 @@ class IterFractalViewer(arcade.gui.UIView): 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.zoom_label = self.info_box.add(arcade.gui.UILabel(text=f"Zoom: {self.zoom}", font_name="Roboto", font_size=16)) + self.max_iter_label = self.info_box.add(arcade.gui.UILabel(text=f"Max Iterations: {self.max_iter}", font_name="Roboto", 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() diff --git a/game/shader.py b/game/shader.py index b81d52e..375c821 100644 --- a/game/shader.py +++ b/game/shader.py @@ -166,6 +166,46 @@ multibrot_calc = """int calculate_iters(vec2 c) {{ }} """ +mandelbar_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; +}} +""" + +multi_mandelbar_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); @@ -183,6 +223,54 @@ burning_ship_calc = """int calculate_iters({vec2type} c) {{ }} """ +phoenix_fractal_calc = """int calculate_iters({vec2type} c) {{ + int iters = 0; + {vec2type} z = {vec2type}(0.0, 0.0); + {vec2type} z_prev = {vec2type}(0.0, 0.0); + {floattype} p = 0.56667; + {floattype} R = {escape_radius}; + + while (dot(z, z) < R * R && iters < u_maxIter) {{ + {vec2type} z_new = {vec2type}( + z.x * z.x - z.y * z.y + c.x - p * z_prev.x, + 2.0 * z.x * z.y + c.y - p * z_prev.y + ); + + z_prev = z; + z = z_new; + iters++; + }} + + return iters; +}} +""" + +lambda_fractal_calc = """int calculate_iters({vec2type} c) {{ + int iters = 0; + {vec2type} z = {vec2type}(0.5, 0.0); // Try nonzero start + float R = {escape_radius}; // Try R = 2.0 if needed + + while (dot(z, z) < R * R && iters < u_maxIter) {{ + {vec2type} one_minus_z = {vec2type}(1.0, 1.0) - z; + + {vec2type} temp = {vec2type}( + z.x * one_minus_z.x - z.y * one_minus_z.y, + z.x * one_minus_z.y + z.y * one_minus_z.x + ); + + z = {vec2type}( + c.x * temp.x - c.y * temp.y, + c.x * temp.y + c.y * temp.x + ); + + 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); }} @@ -269,6 +357,18 @@ def create_iter_calc_shader(fractal_type, width, height, precision="single", mul else: replacements["iter_calc_func"] = multibrot_calc.format_map(replacements) + elif fractal_type == "mandelbar": + if int(multi_n) == 2: + replacements["iter_calc_func"] = mandelbar_calc.format_map(replacements) + else: + replacements["iter_calc_func"] = multi_mandelbar_calc.format_map(replacements) + + elif fractal_type == "phoenix_fractal": + replacements["iter_calc_func"] = phoenix_fractal_calc.format_map(replacements) + + elif fractal_type == "lambda_fractal": + replacements["iter_calc_func"] = lambda_fractal_calc.format_map(replacements) + elif fractal_type == "julia": if int(multi_n) == 2: replacements["iter_calc_func"] = normal_julia_calc.format_map(replacements) diff --git a/game/sierpinsky_carpet.py b/game/sierpinsky_carpet.py index e4dd534..7a6fc27 100644 --- a/game/sierpinsky_carpet.py +++ b/game/sierpinsky_carpet.py @@ -38,8 +38,8 @@ class SierpinskyCarpetViewer(arcade.gui.UIView): 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.depth_label = self.info_box.add(arcade.gui.UILabel(text=f"Depth: {self.depth}", font_name="Protest Strike", font_size=16)) + self.zoom_label = self.info_box.add(arcade.gui.UILabel(text=f"Zoom: {self.zoom}", font_name="Roboto", font_size=16)) + self.depth_label = self.info_box.add(arcade.gui.UILabel(text=f"Depth: {self.depth}", font_name="Roboto", 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() diff --git a/menus/fractal_chooser.py b/menus/fractal_chooser.py index 37769f8..5fd765d 100644 --- a/menus/fractal_chooser.py +++ b/menus/fractal_chooser.py @@ -15,10 +15,10 @@ class FractalChooser(arcade.gui.UIView): self.anchor = self.add_widget(arcade.gui.UIAnchorLayout(size_hint=(1, 1))) - self.grid = self.add_widget(arcade.gui.UIGridLayout(row_count=3, column_count=3, horizontal_spacing=10, vertical_spacing=10)) + self.grid = self.add_widget(arcade.gui.UIGridLayout(row_count=4, column_count=3, horizontal_spacing=10, vertical_spacing=10)) self.anchor.add(self.grid, anchor_x="center", anchor_y="center") - self.title_label = self.anchor.add(arcade.gui.UILabel(text="Choose a fractal to view.", font_name="Protest Strike", font_size=32), anchor_x="center", anchor_y="top", align_y=-50) + self.title_label = self.anchor.add(arcade.gui.UILabel(text="Choose a fractal to view.", font_name="Roboto", font_size=32), anchor_x="center", anchor_y="top", align_y=-50) 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() @@ -27,13 +27,13 @@ class FractalChooser(arcade.gui.UIView): for n, fractal_name in enumerate(iter_fractals): row = n // 3 col = n % 3 - self.iter_fractal_buttons.append(self.grid.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=fractal_name.replace("_", " ").capitalize(), style=button_style, width=200, height=200), row=row, column=col)) - self.mandelbrot_button.on_click = lambda event, fractal_name=fractal_name: self.iter_fractal(fractal_name) + self.iter_fractal_buttons[-1].on_click = lambda event, fractal_name=fractal_name: self.iter_fractal(fractal_name) - row = n // 3 - col = n % 3 - self.sierpinsky_carpet_button = self.grid.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='Sierpinsky Carpet', style=button_style, width=200, height=200), row=0, column=n + 1) + row = (n + 1) // 3 + col = (n + 1) % 3 + + self.sierpinsky_carpet_button = self.grid.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='Sierpinsky Carpet', style=button_style, width=200, height=200), row=row, column=col) self.sierpinsky_carpet_button.on_click = lambda event: self.sierpinsky_carpet() def main_exit(self): diff --git a/menus/main.py b/menus/main.py index 382b1a8..c514be3 100644 --- a/menus/main.py +++ b/menus/main.py @@ -50,7 +50,7 @@ class Main(arcade.gui.UIView): def on_show_view(self): super().on_show_view() - self.title_label = self.box.add(arcade.gui.UILabel(text="Fractal Viewer", font_name="Protest Strike", font_size=48)) + self.title_label = self.box.add(arcade.gui.UILabel(text="Fractal Viewer", font_name="Roboto", font_size=48)) self.play_button = self.box.add(arcade.gui.UITextureButton(text="Play", texture=button_texture, texture_hovered=button_hovered_texture, width=self.window.width / 2, height=150, style=big_button_style)) self.play_button.on_click = lambda event: self.play() diff --git a/menus/settings.py b/menus/settings.py index f470ee1..0e39268 100644 --- a/menus/settings.py +++ b/menus/settings.py @@ -58,7 +58,7 @@ class Settings(arcade.gui.UIView): def display_categories(self): for category in settings: - category_button = arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=category, style=button_style, width=self.window.width / 10, height=50) + category_button = arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=category, style=button_style, width=self.window.width / 13, height=50) if not category == "Credits": category_button.on_click = lambda e, category=category: self.display_category(category) @@ -82,7 +82,7 @@ class Settings(arcade.gui.UIView): self.value_layout.clear() for setting in settings[category]: - label = arcade.gui.UILabel(text=setting, font_name="Protest Strike", font_size=28, text_color=arcade.color.WHITE ) + label = arcade.gui.UILabel(text=setting, font_name="Roboto", font_size=28, text_color=arcade.color.WHITE ) self.key_layout.add(label) setting_dict = settings[category][setting] @@ -261,7 +261,7 @@ class Settings(arcade.gui.UIView): else: font_size = 12 - self.credits_label = arcade.gui.UILabel(text=text, text_color=arcade.color.WHITE, font_name="Protest Strike", font_size=font_size, align="center", multiline=True) + self.credits_label = arcade.gui.UILabel(text=text, text_color=arcade.color.WHITE, font_name="Roboto", font_size=font_size, align="center", multiline=True) self.key_layout.add(self.credits_label) diff --git a/utils/constants.py b/utils/constants.py index 56a9aaa..587afaf 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -9,6 +9,9 @@ discord_presence_id = 1365949409254441000 initial_real_imag = { "mandelbrot": (-2.0, 1.0, -1.0, 1.0), + "mandelbar": (-2.0, 1.0, -1.0, 1.0), + "phoenix_fractal": (-2.0, 1.0, -1.0, 1.0), + "lambda_fractal": (-2.0, 1.0, -1.0, 1.0), "burning_ship": (-2.0, 1.5, -2.0, 1.0), "newton_fractal": (-2.0, 2.0, -2.0, 2.0) } @@ -20,15 +23,15 @@ c_for_julia_type = { "Snowflake": (-0.8, 0.156) } -iter_fractals = ["mandelbrot", "julia", "burning_ship", "newton_fractal"] +iter_fractals = ["mandelbrot", "mandelbar", "phoenix_fractal", "lambda_fractal", "julia", "burning_ship", "newton_fractal"] -button_style = {'normal': UITextureButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK), 'hover': UITextureButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK), - 'press': UITextureButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK), 'disabled': UITextureButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK)} -big_button_style = {'normal': UITextureButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK, font_size=26), 'hover': UITextureButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK, font_size=26), - 'press': UITextureButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK, font_size=26), 'disabled': UITextureButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK, font_size=26)} +button_style = {'normal': UITextureButtonStyle(font_name="Roboto", font_color=arcade.color.BLACK), 'hover': UITextureButtonStyle(font_name="Roboto", font_color=arcade.color.BLACK), + 'press': UITextureButtonStyle(font_name="Roboto", font_color=arcade.color.BLACK), 'disabled': UITextureButtonStyle(font_name="Roboto", font_color=arcade.color.BLACK)} +big_button_style = {'normal': UITextureButtonStyle(font_name="Roboto", font_color=arcade.color.BLACK, font_size=26), 'hover': UITextureButtonStyle(font_name="Roboto", font_color=arcade.color.BLACK, font_size=26), + 'press': UITextureButtonStyle(font_name="Roboto", font_color=arcade.color.BLACK, font_size=26), 'disabled': UITextureButtonStyle(font_name="Roboto", font_color=arcade.color.BLACK, font_size=26)} -dropdown_style = {'normal': UIFlatButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK, bg=Color(128, 128, 128)), 'hover': UIFlatButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK, bg=Color(49, 154, 54)), - 'press': UIFlatButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK, bg=Color(128, 128, 128)), 'disabled': UIFlatButtonStyle(font_name="Protest Strike", font_color=arcade.color.BLACK, bg=Color(128, 128, 128))} +dropdown_style = {'normal': UIFlatButtonStyle(font_name="Roboto", font_color=arcade.color.BLACK, bg=Color(128, 128, 128)), 'hover': UIFlatButtonStyle(font_name="Roboto", font_color=arcade.color.BLACK, bg=Color(49, 154, 54)), + 'press': UIFlatButtonStyle(font_name="Roboto", font_color=arcade.color.BLACK, bg=Color(128, 128, 128)), 'disabled': UIFlatButtonStyle(font_name="Roboto", font_color=arcade.color.BLACK, bg=Color(128, 128, 128))} slider_default_style = UISliderStyle(bg=Color(128, 128, 128), unfilled_track=Color(128, 128, 128), filled_track=Color(49, 154, 54)) slider_hover_style = UISliderStyle(bg=Color(49, 154, 54), unfilled_track=Color(128, 128, 128), filled_track=Color(49, 154, 54)) @@ -43,6 +46,25 @@ settings = { "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} }, + "Mandelbar": { + "Float Precision": {"type": "option", "options": ["Single", "Double"], "config_key": "mandelbar_precision", "default": "Single"}, + "N": {"type": "slider", "min": 1, "max": 10, "config_key": "mandelbar_n", "default": 2, "step": 1}, + "Escape Radius": {"type": "slider", "min": 1, "max": 10, "config_key": "mandelbar_escape_radius", "default": 2, "step": 0.1}, + "Zoom Increase Per Click": {"type": "slider", "min": 2, "max": 100, "config_key": "mandelbar_zoom_increase", "default": 2}, + "Max Iterations": {"type": "slider", "min": 100, "max": 10000, "config_key": "mandelbar_max_iter", "default": 200, "step": 100} + }, + "Phoenix Fractal": { + "Float Precision": {"type": "option", "options": ["Single", "Double"], "config_key": "phoenix_fractal_precision", "default": "Single"}, + "Escape Radius": {"type": "slider", "min": 1, "max": 10, "config_key": "phoenix_fractal_escape_radius", "default": 2, "step": 0.1}, + "Zoom Increase Per Click": {"type": "slider", "min": 2, "max": 100, "config_key": "phoenix_fractal_zoom_increase", "default": 2}, + "Max Iterations": {"type": "slider", "min": 100, "max": 10000, "config_key": "phoenix_fractal_max_iter", "default": 200, "step": 100} + }, + "Lambda Fractal": { + "Float Precision": {"type": "option", "options": ["Single", "Double"], "config_key": "phoenix_fractal_precision", "default": "Single"}, + "Escape Radius": {"type": "slider", "min": 1, "max": 10, "config_key": "phoenix_fractal_escape_radius", "default": 2, "step": 0.1}, + "Zoom Increase Per Click": {"type": "slider", "min": 2, "max": 100, "config_key": "phoenix_fractal_zoom_increase", "default": 2}, + "Max Iterations": {"type": "slider", "min": 100, "max": 10000, "config_key": "phoenix_fractal_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},