プログラミング言語「Python」で制作 Part121 | Photoshop CC Tutorials

今回はプログラミング言語の「Python」を使って作成しました。

 

下記の本のサンプルの「炎のシミュレーション」をPygameで動くように移植しました。

 

■ プログラム

import traceback

try:
    import random, math, time
    import sys
    import pygame
    from pygame.locals import QUIT

    def array(N1, N2=0, N3=0):
        if N2 == 0 and N3==0:return [0 for i in range(N1)]
        elif N3 == 0:return [[0 for i in range(N2)] for j in range(N1)]
        else:return [[[0 for i in range(N3)] for j in range(N2)] for k in range(N1)]

    bitmap=array(200,200)
    Xmax = 5; Ymax = 10; MG=1; #DispR = 1
    EPSI = 0.5;SIGMA = 0.25;Sig2 = 0.5 * SIGMA
    XSig2 = Xmax - Sig2;YSig2 = Ymax - Sig2
    Xmax2=Xmax/2
    N=2000                   # 粒子の数
    NCof=math.sqrt(2000/N)   # 粒子数の正規化比(2000を標準とする)
    DT=0.2                   # 時間刻み
    timerInt=10              # 表示時間間隔
    R=array(2,N,2); F=array(N,2); V=array(2,N,2) #Bname=array(N)
    Btemp=array(N); tFact=0.999
    E1 = -48 * EPSI * pow(SIGMA,13)
    E2 = 24 * EPSI * pow(SIGMA, 7)

    # Pygame初期設定
    pygame.init()
    SURFACE = pygame.display.set_mode((200, 200))
    FPSCLOCK = pygame.time.Clock()
    fps = 60

    src = pygame.Surface((200, 200))
    data = pygame.PixelArray(src)

    #■分子生成
    for i in range(N):
        R[0][i][0] = random.random() * (Xmax - 2.0) + 1.0
        R[0][i][1] = random.random() * (Ymax - 2.0) + 1.0

    #■表示
    Nrend=17; NrendC=int((Nrend-1)/2);NrendC2=NrendC*NrendC

    #■レンダリング用テーブル作成
    def setRendTab():        #表示中に計算するのは効率が悪いので
        global Nrend, NrendC #この表を用意しておく
        A=array(Nrend,Nrend)
        for i in range(Nrend):
            x=i-NrendC
            for j in range(Nrend):
                y=j-NrendC
                r=math.sqrt(x*x+y*y)
                if r<NrendC:
                    h=r-NrendC; A[i][j]=h*h/NrendC2
        return A

    #■値の範囲チェック            
    def checkRange(D, D1=0, D2=255):
        if D<D1: return D1
        if D>D2: return D2
        return int(D)

    #■表示
    def dsp(ID1):
        global N, canvas, R, rendTab,Nrend, NrendC,bitmap, RGBFlag, data
        Tmap=array(200,200)
        #各セルのエネルギー合計を求める
        Xmn=200; Xmx=0; Ymn=200; Ymx=0
        for i in range(N):      
            X =  int(R[ID1][i][0]*20+50)
            Y =  int(R[ID1][i][1]*20)
            if Btemp[i]>=0.24: #元々エネルギーが小さい粒子については計算しない
                Xmn=min(X,Xmn); Ymn=min(Y,Ymn)
                Xmx=max(X,Xmx); Ymx=max(Y,Ymx)
                for k1 in range(-NrendC,NrendC+1):  #粒子が広がりを持っている
                    YY = Y + k1                     #ものとしてエネルギーの
                    if YY>=0 and YY<200:            #合計値を計算する
                        i1=k1+NrendC
                        for k2 in range(-NrendC,NrendC+1):
                            XX = X + k2
                            if XX>=0 and XX<200:
                                Tmap[XX][YY] += rendTab[k2+NrendC][i1]*Btemp[i]
        #粒子のレンダリング範囲を設定
        Xmn-=NrendC; Ymn-=NrendC; Xmx+=NrendC; Ymx+=NrendC
        Xmn=checkRange(Xmn,0,200); Ymn=checkRange(Ymn,0,200)
        Xmx=checkRange(Xmx,0,200); Ymx=checkRange(Ymx,0,200)
        if Xmn<Xmx and Ymn<Ymx:#範囲矛盾のチェック(安全のため)
            for i in range(Xmn,Xmx):#レンダリング
                for j in range(Ymn,Ymx):
                    if Tmap[i][j] >=1.0:
                        TT=Tmap[i][j]*NCof # 2,000個の合計値を標準とする
                        if TT<=5:
                            TT1=TT-1
                            RR=checkRange(63.75*TT1)  # 係数 255/4=63.75
                            G =checkRange(16*TT)      # 係数 64/4=16
                            B=0
                        elif TT<=20:                  # 係数 191/25=7.64
                            RR=255; G=checkRange(64+7.64*(TT-5));B=0
                        else:# 係数 255/30=85
                            RR=255; G=255; B = checkRange(85*(TT-30))
                        if RR !=0 or G!=0 :
                            data[i][j] = (RR, G, B)

    #■力の計算
    def force(ID1):
        global N, F, R
        for i in range(N):
            #以下3行、揺らぎを与えるとき
            #if R[ID1][i][1]< 5:F[i][0] = (random.random()-0.8*random.random())*3
            #else: F[i][0] = (random.random()-1.0*random.random())*3
            #F[i][1] = -random.random() *1.5 #上向きのランダムな力
            #以下2行、揺らぎを与えないとき
            F[i][0] = (random.random()-0.5)*3
            F[i][1] = -random.random() *1.5 #上向きのランダムな力

    #■速度計算
    def velocity(ID1,ID2):
        global N, F, V,MG;  DMG=DT/MG
        for i in range(N):
            V[ID2][i][0] = V[ID1][i][0] + DMG * F[i][0]
            V[ID2][i][1] = V[ID1][i][1] + DMG * F[i][1]
        T = 0 # 温度境界条件
        for i in range(N):
            VX = V[ID2][i][0]; VY = V[ID2][i][1]
            T += VX * VX + VY * VY
        if T > 0.001:
            T = math.sqrt(T) * 0.1
            for i in range(N):
                V[ID2][i][0] /= T; V[ID2][i][1] /= T

    #■位置計算
    def place(ID1,ID2):
        global N, V, X, Btemp,tFact
        XX2=Xmax/2
        for i in range(N):
            R[ID2][i][0] = R[ID1][i][0] + DT * V[ID2][i][0]
            R[ID2][i][1] = R[ID1][i][1] + DT * V[ID2][i][1]
            XX=R[ID2][i][0]-XX2      # 水平方向のエネルギー低下が大きいとする
            YY=(R[ID2][i][1]-Ymax)*0.05
            RR=math.sqrt(XX*XX+YY*YY)
            Btemp[i] *= tFact*math.exp(-0.05*RR)

    #■壁への衝突
    def wallHit(ID2):
        global Sig2,XSig2, YSig2, Xmax2
        for i in range(N): #上壁(天井)の衝突だけを残す
            if R[ID2][i][1] < Sig2:  # 煙の粒子が天井に届いたら
                R[ID2][i][0]=Xmax2   # 下から発生する粒子として
                R[ID2][i][1]=YSig2   # 再利用
                Btemp[i]  =1.0

    #■シミュレーション
    def simulate(ID1, ID2):
        dsp(ID1);force(ID1); velocity(ID1,ID2)
        place(ID1,ID2);  wallHit(ID2)

    #■初期処理用シミュレーション
    def simulate2(ID1, ID2):
        force(ID1); velocity(ID1,ID2)
        place(ID1,ID2);  wallHit(ID2)

    #■メイン処理
    rendTab=setRendTab()
    ID1=0; ID2=1-ID1

    #■粒子が連続的に発生するよう初期調整
    simulate2(ID1,ID2); ID1=ID2
    checkN=int(N/100);k=0; NK=1
    for i in range(N):
        k+=1
        if (k % checkN) ==0:
            print("準備中です(%2d %%)" % int(i/N*100))
            print("■"*NK)
            NK+=1
            if NK>20: NK=1
        R[ID2][i][0]=Xmax/2;R[ID2][i][1]=Ymax-(0.5*SIGMA)
        Btemp[i]=1.0  #表示しないのでtime.sleep(0.001000)は不要
        simulate2(ID1,ID2)

    simulate(ID1,ID2); ID1=ID2

    #■シミュレーション開始
    while True:
        for _ in pygame.event.get(QUIT):
            pygame.quit()
            sys.exit()

        ID2=1-ID1

        #----------------------------------------------------
        # dataに炎のRGBを格納する
        #----------------------------------------------------
        data = pygame.PixelArray(src)
        simulate(ID1,ID2)
        del data

        SURFACE.blit(src, (0, 0))
        pygame.display.update()
        FPSCLOCK.tick(fps)

        ID1=ID2

except Exception as e:
    print("エラー情報\n" + traceback.format_exc())

input()

 

■ 参考書

「Python 3.6 による 力学シミュレーション 第4巻: 分子」

 

■ ゲーム用ライブラリ

「Pygame」

 

■ プログラミング言語

「Python」