The Nature of Code閱讀心得與Python實作:4.8 Particle Systems with...

更新於 2024/10/21閱讀時間約 25 分鐘
這一節的標題是
4.8 Particle Systems with Repellers
因為方格子標題字數限制,所以沒完整顯現

在第二章模擬萬有引力時,曾經利用它來設計會吸引物體的吸子(attractor)。現在,如果想要在模擬粒子系統時,加入會排斥物體的斥子(repeller),那要怎麼做呢?

在原來的粒子系統中,我們加入了重力的作用,這樣粒子從發射器噴射出來之後,才會向下掉。我們想要新加入的斥子,它的的作用方式和重力的作用方式,有相當大的不同。對於每個粒子而言,不管身在何處,重力都一樣大;但是斥子所施加在不同粒子的作用力大小,卻跟粒子和斥子間的距離大小有關。所以,我們必須針對每個粒子去計算,到底斥子施加了多少作用力給它。

要在模擬粒子系統時加入斥子的作用力,需要在原來的程式中加入一個Repeller物件,以及一個將Repeller物件傳入粒子系統發射器的方法,來讓斥子把作用力作用在每一個粒子上。這時候,主程式會長這樣:

gravity = pygame.Vector2(0, 0.1)

emitter = Emitter(320, 60, 0.5)

# 建立Repeller物件
repeller = Repeller(320, 250, 16)

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

screen.fill(WHITE)

emitter.add_particle()
emitter.apply_force(gravity)
# 將Repeller物件傳入粒子系統發射器,以便讓斥子把斥力作用在粒子上
emitter.apply_repeller(repeller)
emitter.run()

repeller.show()

pygame.display.update()
frame_rate.tick(FPS)

當然啦,我們必須先設計出Repeller類別,才能建造Repeller物件。因為Repeller類別和Example 2.6中的Attractor類別其實大同小異,所以只要稍微修改一下Attractor類別,就可以完成Repeller類別的設計。

在設計Attractor類別時,因為依據的是萬有引力公式,引力的強度會受到物體質量和萬有引力常數的影響。在設計Repeller類別時,我們稍微簡化一下這部分,把物體的質量和萬有引力常數整合成一個變數power,並用它來調控斥子斥力的強度。所以,計算斥力的式子,會變成

strength = power/distance**2

Repeller類別中,我們設計了repel()方法,用來讓斥子的斥力作用在粒子上,程式如下:

def repel(self, particle):
d = self.position - particle.position
r_hat = d.normalize() if d.length() > 0 else pygame.Vector2(0, 0)

# 限制距離數值的範圍,避免極端狀況發生
distance = d.magnitude()
if distance < 5:
distance = 5
elif distance > 50:
distance = 50

strength = self.power/distance**2
force = -strength*r_hat

return force

最後在計算force時,前面加上了個負號,那是因為r_hat的寫法是直接從Attractor類別的attract()方法複製而來,加上負號,才能讓力量從吸力變成斥力。

設計好Repeller類別之後,再來就是要設計一個Emitter類別的方法,讓Repeller物件能傳入發射器,以便讓斥力作用在每一個粒子上,程式如下:

def apply_repeller(self, repeller):
for particle in self.particles:
force = repeller.repel(particle)
particle.apply_force(force)

這個方法跟apply_force()方法很像,不過有一個很大的不同點:apply_force()方法傳入的是已經計算好,放在pygame.Vector2物件中的作用力;但是apply_repeller()方法傳入的,則是Repeller物件,然後再計算作用在各個粒子上不同的斥力。

下面這個例子模擬的,就是在粒子系統中放一個斥子的情形。程式部分,因為Particle類別沒有任何變動,所以不再列出。

Example 4.7: A Particle System with a Repeller

raw-image
class Emitter:
def __init__(self, x, y, mass):
self.mass = mass
self.size = 16*self.mass

self.particles = []

# 發射器位置
self.origin = pygame.Vector2(x, y)

def add_particle(self):
self.particles.append(Particle(self.origin.x, self.origin.y, self.mass))

def apply_force(self, force):
for particle in self.particles:
particle.apply_force(force)

def apply_repeller(self, repeller):
for particle in self.particles:
force = repeller.repel(particle)
particle.apply_force(force)

def run(self):
for particle in self.particles:
particle.update()

# 移除壽命已到的粒子
self.particles = list(filter(lambda particle: not particle.is_dead(), self.particles))

for particle in self.particles:
particle.show()


class Repeller:
def __init__(self, x, y, size=1, power=150):
self.screen = pygame.display.get_surface()

self.position = pygame.Vector2(x, y)

self.size = size
self.radius = self.size/2

self.power = power

# 設定repeller所在surface的格式為per-pixel alpha
self.surface = pygame.Surface((self.size, self.size), pygame.SRCALPHA)

def repel(self, particle):
d = self.position - particle.position
r_hat = d.normalize() if d.length() > 0 else pygame.Vector2(0, 0)

# 限制距離數值的範圍,避免極端狀況發生
distance = d.magnitude()
if distance < 5:
distance = 5
elif distance > 50:
distance = 50

strength = self.power/distance**2
force = -strength*r_hat

return force

def show(self):
center = (self.radius, self.radius)
color = (0, 0, 0, 150)
pygame.draw.circle(self.surface, color, center, self.radius)

# 把repeller所在的surface貼到最後要顯示的畫面上
self.screen.blit(self.surface, self.position-center)


# python version 3.10.9
import random
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Example 4.7: A Particle System with a Repeller")

WHITE = (255, 255, 255)

screen_size = 640, 360
screen = pygame.display.set_mode(screen_size)

FPS = 60
frame_rate = pygame.time.Clock()

gravity = pygame.Vector2(0, 0.1)

emitter = Emitter(320, 60, 0.5)

# 建立Repeller物件
repeller = Repeller(320, 250, 16)

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

screen.fill(WHITE)

emitter.add_particle()
emitter.apply_force(gravity)
# 將Repeller物件傳入粒子系統發射器,以便讓斥子把斥力作用在粒子上
emitter.apply_repeller(repeller)
emitter.run()

repeller.show()

pygame.display.update()
frame_rate.tick(FPS)

Exercise 4.9

raw-image

Particle類別不變。把Emitter類別的apply_repeller()改成apply_force_generator()

def apply_force_generator(self, generator):
for particle in self.particles:
force = generator.generate_force(particle)
particle.apply_force(force)

修改Attractor類別

class Attractor:
def __init__(self, x, y, mass, G=1, distance_range=[5, 25]):
self.screen = pygame.display.get_surface()

self.position = pygame.Vector2(x, y)
self.mass = mass
self.G = G
# 用於限制距離數值的範圍,避免極端狀況發生
self.distance_range = distance_range

self.size = self.mass
self.radius = self.size/2

# 設定attractor所在surface的格式為per-pixel alpha
self.surface = pygame.Surface((self.size, self.size), pygame.SRCALPHA)

def generate_force(self, mover):
d = self.position - mover.position
r_hat = d.normalize() if d.length() > 0 else pygame.Vector2(0, 0)

# 限制距離數值的範圍,避免極端狀況發生
distance = d.magnitude()
dmin, dmax = min(self.distance_range), max(self.distance_range)
if distance < dmin:
distance = dmin
elif distance > dmax:
distance = dmax

strength = self.G*self.mass*mover.mass/distance**2
return strength*r_hat

def show(self):
# 使用具透明度的白色把attractor所在的surface清空
self.surface.fill((255, 255, 255, 0))

center = pygame.Vector2(self.radius, self.radius)
color = (0, 0, 0, 150)
pygame.draw.circle(self.surface, color, center, self.radius)

# 把attractor所在的surface貼到最後要顯示的畫面上
self.screen.blit(self.surface, self.position-center)

改用繼承方式來寫Repeller類別

class Repeller(Attractor):
def __init__(self, x, y, size=1, power=150, distance_range=[5, 50]):
super().__init__(x, y, size, power, distance_range)

def generate_force(self, particle):
return -super().generate_force(particle)/self.mass/particle.mass

主程式

# python version 3.10.9
import random
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Exercise 4.9")

WHITE = (255, 255, 255)

WIDTH, HEIGHT = screen_size = 640, 360
screen = pygame.display.set_mode(screen_size)

FPS = 60
frame_rate = pygame.time.Clock()

gravity = pygame.Vector2(0, 0.05)

emitter = Emitter(WIDTH/2, 60, 0.5)

attractors = [Attractor(WIDTH/2+155, 150, 20, 5),
Attractor(WIDTH/2-155, 150, 20, 5)]

repellers = [Repeller(WIDTH/2+25, 255, 16, 55),
Repeller(WIDTH/2-25, 255, 16, 55)]

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

screen.fill(WHITE)

emitter.add_particle()

emitter.apply_force(gravity)

for generator in attractors+repellers:
generator.show()
emitter.apply_force_generator(generator)

emitter.run()

pygame.display.update()
frame_rate.tick(FPS)

Exercise 4.10

raw-image

讓粒子之間有吸力,每個粒子都會和其他粒子互相吸引。

Emitter類別新增interact()方法

def interact(self):
n = len(self.particles)
# 作用在粒子上的合力
f_acc = [pygame.Vector2(0, 0) for i in range(n)]
for i in range(n-1):
particle_A = self.particles[i]
for j in range(i+1, n):
particle_B = self.particles[j]

# 計算作用在粒子B上,方向由粒子B指向粒子A的作用力
d = particle_A.position - particle_B.position
r_hat = d.normalize() if d.length() > 0 else pygame.Vector2(0, 0)

# 限制距離數值的範圍,避免極端狀況發生
distance = d.magnitude()
if distance < 5:
distance = 5
elif distance > 25:
distance = 25

f = r_hat*particle_A.mass*particle_B.mass/distance**2

# 作用在兩個粒子上的作用力,大小相同,方向相反
f_acc[j] += f
f_acc[i] += -f

for i in range(n):
self.particles[i].apply_force(f_acc[i])

主程式

# python version 3.10.9
import random
import sys

import pygame # version 2.3.0

pygame.init()

pygame.display.set_caption("Exercise 4.10")

WHITE = (255, 255, 255)

WIDTH, HEIGHT = screen_size = 640, 360
screen = pygame.display.set_mode(screen_size)

FPS = 60
frame_rate = pygame.time.Clock()

gravity = pygame.Vector2(0, 0.05)

emitter = Emitter(WIDTH/2, 60, 0.5)

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

screen.fill(WHITE)

emitter.add_particle()
emitter.apply_force(gravity)
emitter.interact()
emitter.run()

pygame.display.update()
frame_rate.tick(FPS)


avatar-img
15會員
130內容數
寫點東西自娛娛人
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
ysf的沙龍 的其他內容
到目前為止,我們所設計的粒子系統,都只有受到重力的作用。那如果作用力是風力呢?又該怎麼設計?
接下來,我們會藉由繼承(inheritance)和多型(polymorphism)這兩個物件導向程式設計的技術,來製作更多樣化、更有趣的粒子系統。
有了描述個別粒子的Particle類別之後,這一節就來看看要怎麼做,才能同時掌握許多粒子的動向,特別是這些粒子的數量是隨時都在變動的。
在開始真正處理粒子系統之前,得先寫個用來描述單一粒子的類別。這個類別,就把它叫做Particle。
之所以要研究粒子系統,除了可以用來模擬許多自然界中的現象之外,另一個更重要的原因是:在我們的模擬世界中,會有許多物體存在,而這些物體可能會形成一群一群的群體
粒子系統(particle system)指的是,由許多微小粒子組成,呈現出模糊外觀的物體。這一章的重點會放在探討利用物件導向技術實作粒子系統時,該採用什麼樣的程式架構、描述個別粒子和整個系統的資料該如何管理等方面。
到目前為止,我們所設計的粒子系統,都只有受到重力的作用。那如果作用力是風力呢?又該怎麼設計?
接下來,我們會藉由繼承(inheritance)和多型(polymorphism)這兩個物件導向程式設計的技術,來製作更多樣化、更有趣的粒子系統。
有了描述個別粒子的Particle類別之後,這一節就來看看要怎麼做,才能同時掌握許多粒子的動向,特別是這些粒子的數量是隨時都在變動的。
在開始真正處理粒子系統之前,得先寫個用來描述單一粒子的類別。這個類別,就把它叫做Particle。
之所以要研究粒子系統,除了可以用來模擬許多自然界中的現象之外,另一個更重要的原因是:在我們的模擬世界中,會有許多物體存在,而這些物體可能會形成一群一群的群體
粒子系統(particle system)指的是,由許多微小粒子組成,呈現出模糊外觀的物體。這一章的重點會放在探討利用物件導向技術實作粒子系統時,該採用什麼樣的程式架構、描述個別粒子和整個系統的資料該如何管理等方面。
你可能也想看
Google News 追蹤
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
到目前為止,我們所模擬的萬有引力,是一個物體吸引另一個物體,或者是一個物體吸引多個物體。然而,在真實世界中,每個物體都會互相吸引,所以在這一節中,就來把模擬的情境,擴展成多個物體互相吸引。
Thumbnail
模擬世界是我們寫程式造出來的,我們就是模擬世界的主宰,所以各種作用力要長什麼樣子、要怎麼個作用法,都由我們決定。不過,如果希望這些作用力看起來像真實世界的作用力一樣,那在寫程式的時候,套用這些作用力在真實世界中的物理公式,會是比較省時省力的做法。
在真實世界中有各式各樣的作用力影響著我們,那在模擬世界中呢?要怎麼在本來無一物的模擬世界中,製造出作用力呢?
到目前為止,為了簡化問題,我們都假設物體的質量是1。接下來,我們將移除這個假設,然後將完全符合牛頓第二運動定律的apply_force()方法,整合到Mover這個類別中。
這一節談的是牛頓的三大運動定律,以及力對於物體運動狀態的影響。
介紹以物件導向的方式,以向量來實作物體運動的模擬程式。
Thumbnail
這一節談的是向量的定義,以及如何運用向量來建立模擬物體運動時,關於位置和速度間的關係式。
這一章介紹向量(vector)這個在物理、工程等領域非常重要的數學工具,以及如何用它來模擬一些物理現象。
Thumbnail
這篇要來分享關於「頻率」這件事,談到頻率,不免就要順便談談「吸引力法則」,現在訪間已經有多書籍、影片都有詳細描述吸引力法則的運行方式。它並不是什麼怪力亂神也不是什麼偽科學,實則吸引力法則是個再科學不過的量子力學,同頻相吸的概念而已。
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
到目前為止,我們所模擬的萬有引力,是一個物體吸引另一個物體,或者是一個物體吸引多個物體。然而,在真實世界中,每個物體都會互相吸引,所以在這一節中,就來把模擬的情境,擴展成多個物體互相吸引。
Thumbnail
模擬世界是我們寫程式造出來的,我們就是模擬世界的主宰,所以各種作用力要長什麼樣子、要怎麼個作用法,都由我們決定。不過,如果希望這些作用力看起來像真實世界的作用力一樣,那在寫程式的時候,套用這些作用力在真實世界中的物理公式,會是比較省時省力的做法。
在真實世界中有各式各樣的作用力影響著我們,那在模擬世界中呢?要怎麼在本來無一物的模擬世界中,製造出作用力呢?
到目前為止,為了簡化問題,我們都假設物體的質量是1。接下來,我們將移除這個假設,然後將完全符合牛頓第二運動定律的apply_force()方法,整合到Mover這個類別中。
這一節談的是牛頓的三大運動定律,以及力對於物體運動狀態的影響。
介紹以物件導向的方式,以向量來實作物體運動的模擬程式。
Thumbnail
這一節談的是向量的定義,以及如何運用向量來建立模擬物體運動時,關於位置和速度間的關係式。
這一章介紹向量(vector)這個在物理、工程等領域非常重要的數學工具,以及如何用它來模擬一些物理現象。
Thumbnail
這篇要來分享關於「頻率」這件事,談到頻率,不免就要順便談談「吸引力法則」,現在訪間已經有多書籍、影片都有詳細描述吸引力法則的運行方式。它並不是什麼怪力亂神也不是什麼偽科學,實則吸引力法則是個再科學不過的量子力學,同頻相吸的概念而已。