pythonで「水粒子に関するサンプルプログラム」を作成した。

映画Winnyを先々週何となく見た（東出昌大の演技はよかったが、映画としては駄作）。調べてみると開発者のページがまだ残っていた。全然知らなかったが、物理シミュレーションとかやってた人だったようだ。

物理シミュレーションとpygameの練習なるなと思ったので、pygameに移植してみた。ひねりも何もないサンプルコードだが結構苦戦してしまった。
元ネタはこちら。
* [水粒子に関するサンプルプログラム](http://kaneko-isamu.la.coocan.jp/water2.htm)

## 注意事項
pygameとnumpy必須

## 画面
![ball.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62130/a3baa97b-6be3-1b6b-9e07-f824661e83ce.png)

## コード
```ball.py
import pygame
import sys
from random import uniform
import numpy as np

WHITE = (255,255,255)
BLACK = (  0,  0,  0)
GRAY  = (128,128,128)

DISP_SIZE = (800,600)
CHIP_MAX = 100
R = 15 

def init_chip(mouse_position):
    leftx, top, x, y = frame_around_cursor(mouse_position)
    global bp, obp, dv
    bp  = [np.array([uniform(leftx,leftx+x), uniform(top,top+y), 0.0]) for _ in range(CHIP_MAX)]
    obp = [b.copy() for b in bp]
    dv  = [np.array([0.0,0.0,0.0]) for _ in range(CHIP_MAX)]

def frame_around_cursor(mouse_position):
    BWIDTH  = 200
    BHEIGHT = 200
    mx, my = mouse_position
    return [mx - BWIDTH, my - BHEIGHT, 2*BWIDTH, 2*BHEIGHT]

def draw_cursor(bg,mouse_position):
    mx, my = mouse_position
    pygame.draw.circle(bg, BLACK, [mx,my], 6)
    pygame.draw.rect(bg, GRAY, frame_around_cursor(mouse_position), 3)

def move_chip(mouse_position):
    global bp,obp,dv
    for i in range(CHIP_MAX):
        bp[i] += dv[i]
        bp[i][1] += 0.5
    for k in range(3):
        for j in range(CHIP_MAX):
            for i in range(CHIP_MAX):
                if i < j:
                    vdp = bp[i] - bp[j]
                    dp = np.linalg.norm(vdp) + 0.1
                    if dp < 2*R:
                        # 重なり合った場合には、一方の粒子を反発力を用いて移動させる
                        overlap = (2*R - dp)/2
                        normal = vdp / dp
                        bp[i] += overlap * normal
                        bp[j] -= overlap * normal
    left, top, width, height = frame_around_cursor(mouse_position)
    for i in range(CHIP_MAX):
        if bp[i][0] < R + left:
            bp[i][0] = R + left 
        if bp[i][0] > left + width - R:
            bp[i][0] = left + width - R
        if bp[i][1] < R + top:
            bp[i][1] = R + top
        if bp[i][1] > top + height - R:
            bp[i][1] = top + height - R
    dv = list(map(lambda b,o: b - o, bp, obp))
    obp = [b.copy() for b in bp]

def draw_chip(bg):
    global bp
    for i in range(CHIP_MAX):
        pygame.draw.circle(bg, BLACK, [bp[i][0],bp[i][1]], R)
        pygame.draw.circle(bg, WHITE, [bp[i][0],bp[i][1]], R-1)


bp  = [np.array([0.0,0.0,0.0]) for _ in range(CHIP_MAX)]
obp = [np.array([0.0,0.0,0.0]) for _ in range(CHIP_MAX)]
dv  = [np.array([0.0,0.0,0.0]) for _ in range(CHIP_MAX)]

def main():
    pygame.init()
    pygame.display.set_caption("Kanekoボール")
    screen = pygame.display.set_mode(DISP_SIZE)
    clock = pygame.time.Clock()

    surface = pygame.Surface(DISP_SIZE)
    surface.fill(WHITE)
    #surface.set_alpha(32)
    
    init_chip(pygame.mouse.get_pos())
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_q:
                    pygame.quit()
                    sys.exit()
            
        screen.scroll(1, 4)
        screen.blit(surface, [0,0])
        mpos = pygame.mouse.get_pos()
        draw_cursor(screen,mpos)

        mBtn1, mBtn2, mBtn3 = pygame.mouse.get_pressed()
        if mBtn1:
            init_chip(pygame.mouse.get_pos())

        move_chip(mpos)
        draw_chip(screen)
        pygame.display.update()
        clock.tick(30)

if __name__ == "__main__":
    main()
```