Skip to content

Instantly share code, notes, and snippets.

@salt-die
Last active April 9, 2020 23:18
Show Gist options
  • Save salt-die/35f6140bba668ae70d3fd4645c634850 to your computer and use it in GitHub Desktop.
Save salt-die/35f6140bba668ae70d3fd4645c634850 to your computer and use it in GitHub Desktop.
"""
A simple Mandlebrot explorer in Kivy! Drag mouse to translate, multitouch-zoom to zoom, and scroll to increase/decrease number of iterations.
"""
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.effectwidget import AdvancedEffectBase
from kivy.lang import Builder
from kivy.vector import Vector
SHADER = """
uniform vec2 position;
uniform float zoom;
uniform int iterations;
uniform vec3 palette[16];
const vec2 bottom_left = vec2(-2.0, -1.5);
vec2 pos;
float zx, zy, oldzx;
int n;
vec4 effect(vec4 color, sampler2D texture, vec2 tex_coords, vec2 coords){
pos = (3.0 * tex_coords + bottom_left) / zoom - position + .5;
zx = position.x - .5;
zy = position.y - .5;
for(n = 0; n < iterations; n++){
if(zx * zx + zy * zy > 4.0) break;
oldzx = zx;
zx = zx * zx - zy * zy + pos.x;
zy = 2.0 * oldzx * zy + pos.y;}
if(n < iterations) return vec4(palette[int(mod(float(n), 16.0))], 1.0);
else return vec4(0.0, 0.0, 0.0, 1.0);}
"""
PALETTE = [[0.259, 0.118, 0.059], [0.098, 0.027, 0.102], [0.035, 0.004, 0.184], [0.016, 0.016, 0.286],
[0.0 , 0.027, 0.392], [0.047, 0.173, 0.541], [0.094, 0.322, 0.694], [0.224, 0.49 , 0.82 ],
[0.525, 0.71 , 0.898], [0.827, 0.925, 0.973], [0.945, 0.914, 0.749], [0.973, 0.788, 0.373],
[1.0 , 0.667, 0.0 ], [0.8 , 0.502, 0.0 ], [0.6 , 0.341, 0.0 ], [0.416, 0.204, 0.012]]
EFFECT = AdvancedEffectBase()
EFFECT.glsl = SHADER
EFFECT.uniforms = {'position': (.5, .5), 'zoom': 1.0, 'iterations': 30, 'palette': PALETTE}
KV = """
#:import EFFECT __main__.EFFECT
EffectWidget:
effects: EFFECT,
"""
ZOOM = 500 # multitouch zooming speed (inversely proportional)
TRANSLATE = 3 # multitouch translation speed
class Mandlebrot(App):
def build(self):
self._touches = []
Window.bind(on_touch_up=self._on_touch_up,
on_touch_down=self._on_touch_down,
on_touch_move=self._on_touch_move)
return Builder.load_string(KV)
def transform_on_touch(self, touch):
anchor = Vector(self._touches[-2].pos)
current = Vector(touch.pos) - anchor
previous = Vector(touch.ppos) - anchor
travel_distance = (current.length() - previous.length()) / ZOOM
zoom = EFFECT.uniforms['zoom']
EFFECT.uniforms['zoom'] = max(.000001, zoom * (1 + travel_distance))
def _on_touch_up(self, _, touch):
self._touches.remove(touch)
def _on_touch_down(self, _, touch):
if touch.is_mouse_scrolling:
iterations = EFFECT.uniforms['iterations']
iterations += (-1)**(touch.button == 'scrollup')
EFFECT.uniforms['iterations'] = max(1, iterations)
self._touches.append(touch)
def _on_touch_move(self, _, touch):
if touch.button == 'left':
if len(self._touches) == 1:
x, y = EFFECT.uniforms['position']
zoom = EFFECT.uniforms['zoom']
x += touch.dsx * TRANSLATE / zoom
y += touch.dsy * TRANSLATE / zoom
EFFECT.uniforms['position'] = x, y
else:
self.transform_on_touch(touch)
if __name__ == '__main__':
Mandlebrot().run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment