LoginSignup
0
0

More than 1 year has passed since last update.

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

Posted at

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

物理シミュレーションとpygameの練習なるなと思ったので、pygameに移植してみた。ひねりも何もないサンプルコードだが結構苦戦してしまった。
元ネタはこちら。

注意事項

pygameとnumpy必須

画面

ball.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()
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0