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

更新於 發佈於 閱讀時間約 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
留言分享你的想法!
avatar-img
ysf的沙龍
18會員
146內容數
寫點東西自娛娛人
ysf的沙龍的其他內容
2024/10/25
粒子系統可以用來製作視覺特效(visual effect, VFX),而粒子外觀的呈現方式,以及粒子具有怎樣的紋理(texture),都會影響特效所展現出來的效果。本節介紹如何利用不同紋理的粒子圖片,以不同的混色模式(blending mode),透過粒子系統來製作模擬煙霧的特效。
Thumbnail
2024/10/25
粒子系統可以用來製作視覺特效(visual effect, VFX),而粒子外觀的呈現方式,以及粒子具有怎樣的紋理(texture),都會影響特效所展現出來的效果。本節介紹如何利用不同紋理的粒子圖片,以不同的混色模式(blending mode),透過粒子系統來製作模擬煙霧的特效。
Thumbnail
2024/10/18
到目前為止,我們所設計的粒子系統,都只有受到重力的作用。那如果作用力是風力呢?又該怎麼設計?
2024/10/18
到目前為止,我們所設計的粒子系統,都只有受到重力的作用。那如果作用力是風力呢?又該怎麼設計?
2024/10/14
接下來,我們會藉由繼承(inheritance)和多型(polymorphism)這兩個物件導向程式設計的技術,來製作更多樣化、更有趣的粒子系統。
Thumbnail
2024/10/14
接下來,我們會藉由繼承(inheritance)和多型(polymorphism)這兩個物件導向程式設計的技術,來製作更多樣化、更有趣的粒子系統。
Thumbnail
看更多
你可能也想看
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
全球科技產業的焦點,AKA 全村的希望 NVIDIA,於五月底正式發布了他們在今年 2025 第一季的財報 (輝達內部財務年度為 2026 Q1,實際日曆期間為今年二到四月),交出了打敗了市場預期的成績單。然而,在銷售持續高速成長的同時,川普政府加大對於中國的晶片管制......
Thumbnail
全球科技產業的焦點,AKA 全村的希望 NVIDIA,於五月底正式發布了他們在今年 2025 第一季的財報 (輝達內部財務年度為 2026 Q1,實際日曆期間為今年二到四月),交出了打敗了市場預期的成績單。然而,在銷售持續高速成長的同時,川普政府加大對於中國的晶片管制......
Thumbnail
重點摘要: 6 月繼續維持基準利率不變,強調維持高利率主因為關稅 點陣圖表現略為鷹派,收斂 2026、2027 年降息預期 SEP 連續 2 季下修 GDP、上修通膨預測值 --- 1.繼續維持利率不變,強調需要維持高利率是因為關稅: 聯準會 (Fed) 召開 6 月利率會議
Thumbnail
重點摘要: 6 月繼續維持基準利率不變,強調維持高利率主因為關稅 點陣圖表現略為鷹派,收斂 2026、2027 年降息預期 SEP 連續 2 季下修 GDP、上修通膨預測值 --- 1.繼續維持利率不變,強調需要維持高利率是因為關稅: 聯準會 (Fed) 召開 6 月利率會議
Thumbnail
模擬世界是我們寫程式造出來的,我們就是模擬世界的主宰,所以各種作用力要長什麼樣子、要怎麼個作用法,都由我們決定。不過,如果希望這些作用力看起來像真實世界的作用力一樣,那在寫程式的時候,套用這些作用力在真實世界中的物理公式,會是比較省時省力的做法。
Thumbnail
模擬世界是我們寫程式造出來的,我們就是模擬世界的主宰,所以各種作用力要長什麼樣子、要怎麼個作用法,都由我們決定。不過,如果希望這些作用力看起來像真實世界的作用力一樣,那在寫程式的時候,套用這些作用力在真實世界中的物理公式,會是比較省時省力的做法。
Thumbnail
這一節談的是向量的定義,以及如何運用向量來建立模擬物體運動時,關於位置和速度間的關係式。
Thumbnail
這一節談的是向量的定義,以及如何運用向量來建立模擬物體運動時,關於位置和速度間的關係式。
Thumbnail
這篇要來分享關於「頻率」這件事,談到頻率,不免就要順便談談「吸引力法則」,現在訪間已經有多書籍、影片都有詳細描述吸引力法則的運行方式。它並不是什麼怪力亂神也不是什麼偽科學,實則吸引力法則是個再科學不過的量子力學,同頻相吸的概念而已。
Thumbnail
這篇要來分享關於「頻率」這件事,談到頻率,不免就要順便談談「吸引力法則」,現在訪間已經有多書籍、影片都有詳細描述吸引力法則的運行方式。它並不是什麼怪力亂神也不是什麼偽科學,實則吸引力法則是個再科學不過的量子力學,同頻相吸的概念而已。
Thumbnail
1 引言 微分方程是描述一個系統的狀態隨時間和空間演化的最基本的數學工具之一,其在物理、經濟、工程、社會等各方面都有及其重要的應用。 然而,只有很少的微分方程式可以解析求解,尤其對於偏微分方程,能解析求解的種類更是寥寥可數。 更多的微分方程式可以用數值法來求解,只要精確度夠高,就可以滿足科學和工程
Thumbnail
1 引言 微分方程是描述一個系統的狀態隨時間和空間演化的最基本的數學工具之一,其在物理、經濟、工程、社會等各方面都有及其重要的應用。 然而,只有很少的微分方程式可以解析求解,尤其對於偏微分方程,能解析求解的種類更是寥寥可數。 更多的微分方程式可以用數值法來求解,只要精確度夠高,就可以滿足科學和工程
Thumbnail
目錄 Ch. 1 科學態度方法與緒論 =>物理學的簡介、科學態度是新教材 Ch. 2 物質的組成+交互作用 =>物質組成=>分子=>原子=>原子核+電子(基本粒子)=>質子+中子(夸克:基本粒子) =>四大交互作用=>長程力(電+重)+短程力(強+弱) Ch. 3 物體的運動 =>慣性的思辨(亞里斯
Thumbnail
目錄 Ch. 1 科學態度方法與緒論 =>物理學的簡介、科學態度是新教材 Ch. 2 物質的組成+交互作用 =>物質組成=>分子=>原子=>原子核+電子(基本粒子)=>質子+中子(夸克:基本粒子) =>四大交互作用=>長程力(電+重)+短程力(強+弱) Ch. 3 物體的運動 =>慣性的思辨(亞里斯
Thumbnail
今天的第一個任務是「設計一種測量磁力大小的方法」。 上學期我們曾經針對「力」這個主題進行探討。於是我請少年們把相關的學習單帶來,回顧之前的經驗,再開始進行這個任務......
Thumbnail
今天的第一個任務是「設計一種測量磁力大小的方法」。 上學期我們曾經針對「力」這個主題進行探討。於是我請少年們把相關的學習單帶來,回顧之前的經驗,再開始進行這個任務......
Thumbnail
這篇文章將會講述圓形波的設計思路、製作流程和應用方向。
Thumbnail
這篇文章將會講述圓形波的設計思路、製作流程和應用方向。
Thumbnail
從上次課程,我們知道生活中到處都是力的存在,人們用各種方式去使用力,小到手提重物,大到火箭升空。 藉由精準的測量力,人們得以較精準地預測物體會怎麼被改變、被移動,也因此我們可以做出各式各樣的模擬,減少實際測驗時產生的錯誤......
Thumbnail
從上次課程,我們知道生活中到處都是力的存在,人們用各種方式去使用力,小到手提重物,大到火箭升空。 藉由精準的測量力,人們得以較精準地預測物體會怎麼被改變、被移動,也因此我們可以做出各式各樣的模擬,減少實際測驗時產生的錯誤......
Thumbnail
介紹馬達中的磁阻力(Reluctance force)的產生原因。 講起來有點神奇,但肯定是每個人都有過的經驗,並實際體會過磁阻力的作用。當您手上拿著磁鐵,將磁鐵靠近金屬時,會忽然產生一個吸力,將磁鐵與金屬完全吸附在一起,這股力量就是磁阻力。 重點整理: 磁阻力,就是磁吸力。 馬達顧問服務
Thumbnail
介紹馬達中的磁阻力(Reluctance force)的產生原因。 講起來有點神奇,但肯定是每個人都有過的經驗,並實際體會過磁阻力的作用。當您手上拿著磁鐵,將磁鐵靠近金屬時,會忽然產生一個吸力,將磁鐵與金屬完全吸附在一起,這股力量就是磁阻力。 重點整理: 磁阻力,就是磁吸力。 馬達顧問服務
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News