Skip to content

Instantly share code, notes, and snippets.

@salt-die
Last active June 21, 2020 19:42
Show Gist options
  • Save salt-die/c52905e575b6ca8f4323ff3a485fdb32 to your computer and use it in GitHub Desktop.
Save salt-die/c52905e575b6ca8f4323ff3a485fdb32 to your computer and use it in GitHub Desktop.
Make any widget wobble with wobbly widget!
"""For this to work, WobblyEffect should be parent to WobblyScatter.
"""
from kivy.clock import Clock
from kivy.uix.effectwidget import AdvancedEffectBase, EffectWidget
from kivy.uix.scatter import Scatter
from itertools import product
FRICTION = .95
K = 8
MASS = 25
EPSILON = .001
class WobblyNode:
'''Represents the nodes that springs attach to in a spring mesh.
'''
__slots__ = 'p', 'v', 'a' # position, velocity, acceleration
def __init__(self):
for attr in self.__slots__:
setattr(self, attr, 0j)
def move(self, dx, dy):
self.p += complex(dx, dy)
def step(self):
self.v += (self.a - FRICTION * self.v) / MASS
self.p += self.v
self.a = 0j
return abs(self.v)
@property
def xy(self):
return [self.p.real, self.p.imag]
class Spring:
__slots__ = 'node_1', 'node_2'
def __init__(self, node_1, node_2):
self.node_1 = node_1
self.node_2 = node_2
def step(self):
a = (self.node_2.p - self.node_1.p) * K
self.node_1.a += a
self.node_2.a -= a
class SpringMesh:
__slots__ = '_nodes', 'springs'
def __init__(self):
self._nodes = tuple(WobblyNode() for _ in range(16))
springs = []
for i, j in product(range(4), repeat=2):
if j < 3:
springs.append(Spring(self[i, j], self[i, j + 1]))
if i < 3:
springs.append(Spring(self[i, j], self[i + 1, j]))
self.springs = tuple(springs)
def __getitem__(self, key):
return self._nodes[4 * key[0] + key[1]]
def __iter__(self):
return iter(self._nodes)
BEZIER_PATCH = """
uniform vec2 node_xy[16];
const vec4 bin_coeff = vec4(1.0, 3.0, 3.0, 1.0); // hard-coded binomial coefficients
vec2 new_coords;
vec4 coeff_u, coeff_v;
float bernstein_basis(float u, int deg){
return bin_coeff[deg] * pow(u, float(deg)) * pow(1.0 - u, 3.0 - float(deg));}
vec4 effect(vec4 color, sampler2D texture, vec2 tex_coords, vec2 coords){
for (int i = 0; i < 4; i++){
coeff_u[i] = bernstein_basis(tex_coords.x, i);
coeff_v[i] = bernstein_basis(tex_coords.y, i);}
new_coords = tex_coords;
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++){
new_coords += coeff_u[i] * coeff_v[j] * node_xy[4 * i + j];}}
return texture2D(texture, new_coords);}
"""
class WobblyEffect(EffectWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.bezier = AdvancedEffectBase(glsl=BEZIER_PATCH, uniforms= {'node_xy': [[0, 0] for _ in range(16)]})
self.effects = [self.bezier]
class WobblyScatter(Scatter):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.spring_mesh = SpringMesh()
self.anchor = self.spring_mesh[0, 0]
self.update = Clock.schedule_interval(self.step, 0) # run this while wobbling...
self.update.cancel() # ...and cancel when wobbling slows
def on_touch_down(self, touch):
if self.collide_point(touch.x, touch.y):
anchor_x = round((touch.x - self.x) / self.width / self.scale * 3)
anchor_y = round((touch.y - self.y) / self.height / self.scale * 3)
self.anchor = self.spring_mesh[anchor_x, anchor_y]
return super().on_touch_down(touch)
def on_transform_with_touch(self, touch):
dx = touch.sx - touch.psx
dy = touch.sy - touch.psy
self.anchor.move(dx, dy)
self.update()
def step(self, dt=0):
mesh = self.spring_mesh
for spring in mesh.springs:
spring.step()
total_velocity = sum(node.step() for node in mesh)
if total_velocity < EPSILON:
self.update.cancel()
for i, j in product(range(4), repeat=2):
self.parent.bezier.uniforms['node_xy'][4 * i + j] = mesh[i, j].xy
self.anchor.p = 0j # Move anchor back to starting position.
if __name__ == '__main__':
from kivy.app import App
from kivy.lang import Builder
from textwrap import dedent
class WobbleExample(App):
def build(self):
kv = """
WobblyEffect:
WobblyScatter:
size_hint: None, None
Image:
source: 'python_discord_logo.png'
"""
return Builder.load_string(dedent(kv))
WobbleExample().run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment