映画Winnyを先々週何となく見た(東出昌大の演技はよかったが、映画としては駄作)。調べてみると開発者のページがまだ残っていた。全然知らなかったが、物理シミュレーションとかやってた人だったようだ。
物理シミュレーションとpygameの練習なるなと思ったので、pygameに移植してみた。ひねりも何もないサンプルコードだが結構苦戦してしまった。
元ネタはこちら。
注意事項
pygameとnumpy必須
画面
コード
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()