Forum Archive

Fast color change animation of a shape

papajohn

I'd like to animate color changes for many rectangles at once in response to touch events. In particular, I'd like a large number of rectangles (~1000) to all be transitioning among hues in a spectrum from blue to yellow.

As a first attempt, I created a ShapeNode for each rectangle and changed each one's fill_color to a new value on every touch_moved. The program runs, but only at 2 FPS on an iPad Pro.

I suspect there's a fast way to do this, perhaps using a Shader. I couldn't figure out how to instantiate a Shader just for the purpose of animating color changes.

My code is below, but my specific question is how I would make a rectangle gradually transition between two hues (e.g. blue to yellow) in a performant way, so that I can have many such rectangles. Thanks!

from scene import *
from ui import Path
import math
import random
from colorsys import hsv_to_rgb

YELLOW = 1/6
BLUE = 2/3
S = 24

class Grid(Scene):
    def setup(self):
        width = int(self.bounds.width) // S
        height = int(self.bounds.height) // S
        self.cells = [Cell(i, j, parent=self) for i in range(width) for j in range(height)]

    def touch_moved(self, touch): 
        for cell in self.cells:
            cell.touched += S/abs(cell.frame.center() - touch.location)**2
            cell.update()

class Cell(ShapeNode):
    def __init__(self, i, j, **vargs):
        self.touched = 0
        super().__init__(Path.rect(0, 0, S, S), 'white', position=(i*S, j*S), **vargs)

    def cell_color(self):
        scale = min(1, self.touched)
        hue = scale * YELLOW + (1-scale) * BLUE
        return hsv_to_rgb(hue, 1, 1)    

    def update(self):
        self.fill_color = self.cell_color()

if __name__ == '__main__':
    run(Grid(), show_fps=True)


abcabc

The following code creates a rectangular grid of 64 cells (8*8) with different colors. If you touch a cell, it changes the color. I hope this gives you an idea of how to solve your problem with shader. The following online book explains how to create such shaders.
http://patriciogonzalezvivo.com/2015/thebookofshaders/

import scene, ui

shader_text = '''
precision highp float;
varying vec2 v_tex_coord;
uniform sampler2D u_texture;
uniform float u_time;
uniform vec2 u_sprite_size;
uniform float u_scale;
uniform vec2 u_offset;

void main(void) {
    vec2 uv = mod(v_tex_coord, .125);
    vec2 invuv = uv*8.;
    vec2 pq = floor(v_tex_coord*8.0)/8.;
    //vec4 color = texture2D(u_texture, invuv);
    vec2 t = floor(u_offset*8.0/(u_sprite_size))/8.;  
    float r = 0.;
    if ((pq.x == t.x) && (pq.y == t.y)) r = 1.0; 
    vec4 color = vec4(pq.x, pq.y, r, 1.);
    gl_FragColor = color;
}
'''

class MyScene (scene.Scene):
    def setup(self):
        tile_texture = scene.Texture(ui.Image.named('Snake'))
        self.sprite = scene.SpriteNode(
            tile_texture,
            size=(600, 600),
            anchor_point=(0,0),
            parent=self)
        self.sprite.shader = scene.Shader(shader_text)
        self.sprite.position = (100, 100)

    def touch_began(self, touch):
        self.set_touch_position(touch)
        print (self.sprite.shader.get_uniform('u_offset'))

    def set_touch_position(self, touch):
        dx, dy = touch.location -self.sprite.position
        self.sprite.shader.set_uniform('u_offset', (dx, dy))

scene.run(MyScene(), show_fps=True)