sketch_220220b

sketch.py

# -*- coding: utf-8 -*-
import random
import q5
import numpy as np
import GPy

N = 200
colors = [
    (0, 220, 240), (240, 100, 0)
]


def choice_color():
    return colors[0] if random.random() > 0.05 else colors[1]


class Slime:
    def __init__(self):
        self.x = np.linspace(0, 2 * np.pi, N)
        self.y = np.zeros((1, N))

        self.period = 1.0
        self.r_wave = 80.0
        self.r_body = 250.0
        self.eye_size = 100.0
        self.eye_pos_l = q5.Vector(-100.0, 0.0)
        self.eye_pos_r = q5.Vector(100.0, 0.0)
        self.eye_basis = (0, N // 2)
        self.color = choice_color()

    def set_y(self, y):
        self.y = y

        l = random.randint(0, N // 2) + N // 4
        r = random.randint(0, N // 2 - 1)
        r = r if r <= N // 4 else r + N // 2
        assert N // 4 <= l <= N // 4 * 3, l
        assert 0 <= r <= N // 4 or N // 4 * 3 <= r < N, r

        self.eye_basis = (l, r)

    def draw(self, elapsed_time):
        t = elapsed_time * np.pi * 2.0 / self.period

        r1 = self.r_wave * min(1.0, 1.5 * np.sin(t) ** 4.0)
        r2 = self.r_body + r1 * self.y
        px = r2 * np.cos(self.x)
        py = r2 * np.sin(self.x)

        pts = np.vstack([px, py]).T

        eye_diff_l = q5.Vector(*pts[self.eye_basis[0]])
        eye_diff_r = q5.Vector(*pts[self.eye_basis[1]])

        eye_diff_l = eye_diff_l - eye_diff_l.normalize().mult(self.r_body)
        eye_diff_r = eye_diff_r - eye_diff_r.normalize().mult(self.r_body)

        eye_pos_l = self.eye_pos_l + eye_diff_l.mult(0.25)
        eye_pos_r = self.eye_pos_r + eye_diff_r.mult(0.25)

        q5.push_matrix()

        q5.fill(*self.color)
        q5.polygon(pts)

        q5.push_matrix()
        q5.translate(eye_pos_l.x, eye_pos_l.y)
        q5.fill(240)
        q5.circle(0.0, 0.0, self.eye_size)
        q5.fill(20)
        q5.circle(0.0, 0.0, self.eye_size / 2.0)
        q5.pop_matrix()

        q5.push_matrix()
        q5.translate(eye_pos_r.x, eye_pos_r.y)
        q5.fill(240)
        q5.circle(0.0, 0.0, self.eye_size)
        q5.fill(20)
        q5.circle(0.0, 0.0, self.eye_size / 2.0)
        q5.pop_matrix()

        q5.pop_matrix()


class App(q5.BaseApp):
    def setup(self):
        q5.title('sketch_220220b')
        q5.full_screen(True)
        # q5.loop_ntimes(60 * 60)

        N = 200
        self.period = 1.0

        self.n_rows = 3
        self.n_cols = 5
        self.n_samples = self.n_rows * self.n_cols
        self.cell_scale = 1.0 / min(self.n_rows, self.n_cols)

        self.x = np.linspace(0, 2 * np.pi, N)
        self.y = np.zeros((self.n_samples, N))

        kernel = GPy.kern.src.periodic.PeriodicExponential(
            input_dim=1, variance=1.0, lengthscale=0.05, period=np.pi*2.0
        )

        # Mean function
        self.mu = np.zeros(N)
        # Cov function
        self.cov = kernel.K(self.x[:, np.newaxis],
                            self.x[:, np.newaxis])

        self.slime_grid = []
        for i in range(self.n_rows):
            row = []
            for j in range(self.n_cols):
                row.append(Slime())
            self.slime_grid.append(row)

        self.elapsed_time = 0.0

    def update(self):
        if self.elapsed_time >= self.period / 2.0:
            self.y = np.random.multivariate_normal(
                self.mu, self.cov, size=self.n_samples
            )
            for i in range(self.n_rows):
                for j in range(self.n_cols):
                    self.slime_grid[i][j].set_y(self.y[i*self.n_cols+j, :])
                self.elapsed_time = 0.0
        self.elapsed_time += 1.0 / 60.0

    def draw(self):
        q5.background(220)

        for i in range(self.n_rows):
            for j in range(self.n_cols):
                q5.push_matrix()
                dj = 0.5 if self.n_cols % 2 == 0 else 0.0
                di = 0.5 if self.n_rows % 2 == 0 else 0.0
                q5.translate(
                    (j - self.n_cols // 2.0 + dj) * q5.width / self.n_cols,
                    (i - self.n_rows // 2.0 + di) * q5.height / self.n_rows
                )
                q5.scale(self.cell_scale, self.cell_scale)
                q5.stroke_weight(3.0 * min(self.n_cols, self.n_rows))

                self.slime_grid[i][j].draw(self.elapsed_time)

                q5.pop_matrix()

        # q5.save_frame('frames/{0:05d}.png'.format(q5.frame_count))


if __name__ == '__main__':
    app = App()
    app.run()