The Nature of Code閱讀心得與Python實作:3.9 Spring Forces

更新於 2024/09/16閱讀時間約 1 分鐘

在Exercise 3.7中,我們曾經利用sin函數來模擬彈簧吊錘(bob)的運動,雖然這樣子的做法程式很容易寫,但是卻沒辦法模擬彈簧吊錘受到如風力、重力等環境中其他作用力的影響下,在空間中的運動狀況。要克服這樣子的問題,就不能再倚靠sin函數,而必須改用能夠用來計算彈簧彈力的虎克定律(Hooke's law)。

raw-image

根據虎克定律

彈簧的彈力正比於彈簧的形變量

這裡的形變量,指的是彈簧被拉長或擠短的量;所以,把彈簧拉得越長或擠得越短,彈簧回復原狀的力量就會越大。

raw-image

虎克定律的數學式是

Fspring = -kx

其中,k是彈簧常數(spring constant);x是彈簧的形變量,計算方式是將彈簧當前的長度減去在平衡狀態時的長度。彈簧在平衡狀態時的長度,也稱為靜止長度(rest length)。

因為力是向量,在計算時,必須算出它的大小和方向,所以在寫程式時,我們會利用下列向量變數與純量變數來存放彈簧的資料:

anchor  # 向量,存放錨釘的位置
bob_position # 向量,存放吊錘的位置
rest_length # 純量,存放彈簧的靜止長度

利用虎克定律來計算彈簧的彈力時,需要知道彈簧常數k和彈簧的形變量x。k就只是個常數,所以設定個適合的常數就可以了:

k = 0.1

要計算x,必須知道彈簧當前的長度和靜止長度。彈簧的靜止長度是原本就知道的數字,假設是放在rest_length裡頭。彈簧當前的長度,其實就是錨釘和吊錘之間的距離,也就是由錨釘到吊錘的向量長度。所以

current_length = (bob_position - anchor).length()
x = current_length - rest_length

從x的計算方式可以知道,當x>0時,代表彈簧被拉長;而當x<0時,則代表彈簧被擠短。

有了k和x之後,就可以算出彈簧彈力的大小。那彈簧彈力的方向呢?如前面的圖所顯示的,當彈簧被拉長時,會有一股力量讓它朝著錨釘的方向縮回去;而當彈簧被擠短時,則會有一股力量,讓它朝著遠離錨釘的方向伸長,虎克定律式子中的-1,就是在描述這個方向反轉的現象。所以,想要知道彈簧彈力的方向,就要先知道在拉長彈簧時,施加在彈簧上的拉力的方向。要找出這個拉力的方向其實很簡單,既然彈簧的一端是錨釘,而另一端是吊錘,當我們拉著吊錘讓彈簧變長時,拉力的方向,很顯然的,就是從錨釘到吊錘的方向,也就是向量

bob_position - anchor

的方向。

根據上述的分析,利用虎克定律,我們就可以算出彈簧彈力。計算彈簧彈力的程式,可以這樣寫:

k = 0.1
force = bob_position - anchor
current_length = force.length()
x = current_length - rest_length
force = -k*x*force.normalize()

有了可以計算彈簧彈力的方法之後,接下來就要來看看怎麼用物件導向的方式來寫模擬的程式。不過,在實際動手寫之前,要先考慮一下整個程式的架構要長怎樣,畢竟寫法不只一種。

在模擬彈簧的運動時,整個系統是由彈簧和吊錘所組成,彈簧提供彈力,而吊錘受力之後,就在畫面上跑來跑去。既然吊錘會在畫面上跑來跑去,而我們先前所建立的Mover類別產生的物件,也會在畫面上跑來跑去,那順理成章的,我們可以就把吊錘看成是類似於Mover類別所建立的物件,然後用處理Mover類別的方式來處理。略微修改一下Mover類別,另外再加入阻尼的作用,讓吊錘像真實世界看到的情況那樣,運動速度會慢慢地降下來,最後回到靜止狀態,這樣就可以得到如下用來模擬彈簧吊錘的Bob類別:

class Bob:
def __init__(self, x, y, mass):
# 取得顯示畫面
self.screen = pygame.display.get_surface()

# 讓傳遞進來的數值來決定物體的質量
self.mass = mass

# 物體的質量越大,尺寸就會越大
self.size = 2*self.mass
self.radius = self.size/2

# 加入阻尼,讓振盪速度越來越小
self.damping = 0.98

# 讓傳遞進來的數值來決定物體的起始位置
self.position = pygame.Vector2(x, y)

# 物件的初始速度、初始加速度
self.velocity = pygame.Vector2(0, 0)
self.acceleration = pygame.Vector2(0, 0)

def apply_force(self, force):
self.acceleration += force/self.mass

def update(self):
self.velocity += self.acceleration
self.velocity *= self.damping
self.position += self.velocity

self.acceleration *= 0

def show(self):
pygame.draw.circle(self.screen, (0, 0, 0), self.position, self.radius)

設計好Bob類別之後,接下來就來處理彈簧的部分。我們可以直接把計算彈簧彈力的程式碼全都直接寫主程式中,然後用Bob類別的apply_force()方法,讓彈力作用在吊錘上,像這樣:

spring_force = 算出來的彈簧彈力
bob.apply_force(spring_force)

但是,考慮到同一條彈簧有可能會掛上不只一個吊錘,或者會有好幾條彈簧串在一起的情況,另外設計一個Spring類別來處理彈簧的部分,會是比較好的做法。

Spring類別中,需要有個方法,用來將彈簧彈力作用在吊錘上。在2.6節設計Body類別時,我們設計了attract()這個方法,用來計算引力,並將引力作用在物體上。這樣子的寫法也可以運用到Spring類別上,用來計算彈簧彈力,並將彈力作用在吊錘上。當我們把吊錘吊掛在彈簧上,跟彈簧連接在一起時,彈簧的彈力才會作用在吊錘上;所以,就把這個讓吊錘連上彈簧的方法命名為connect()。有了connect()方法後,當要讓一個spring物件把彈力作用在一個bob物件時,程式就可以寫成

spring.connect(bob)

完整的Spring類別程式碼如下:

class Spring:
def __init__(self, x, y, spring_constant, rest_length):
# 取得顯示畫面
self.screen = pygame.display.get_surface()

# 彈簧常數
self.k = spring_constant

# 錨釘的位置
self.anchor = pygame.Vector2(x, y)

# 彈簧靜止長度
self.rest_length = rest_length

def connect(self, bob):
force = bob.position - self.anchor
current_length = force.length()
x = current_length - self.rest_length # 彈簧形變量
force = -self.k*x*force.normalize()

bob.apply_force(force)

def show(self):
# 畫錨釘
pygame.draw.circle(self.screen, (0, 0, 0), self.anchor, 5)

def show_line(self, bob):
# 畫彈簧線
pygame.draw.line(self.screen, (0, 0, 0, 50), self.anchor, bob.position, 3)

原書在設計Spring類別時,是把彈簧常數直接寫死。不過,考慮到不同的彈簧,可能會有不同的彈簧常數,所以這裡不把彈簧常數寫死,改為在產生Spring物件時設定。這樣子的設計方式,在使用上應該會比較有彈性一點。

Example 3.10: A Spring Connection

# python version 3.10.9
import math
import sys

import pygame # version 2.3.0

pygame.init()

pygame.display.set_caption("Example 3.10: A Spring Connection")

WHITE = (255, 255, 255)

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

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

x, y = width//3, 150
mass = 24
bob = Bob(x, y, mass)

x, y = width//2, 10
spring_constant = 0.2
rest_length = 100
spring = Spring(x, y, spring_constant, rest_length)

gravity = pygame.Vector2(0, 2)

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

screen.fill(WHITE)

bob.apply_force(gravity)

spring.connect(bob)

bob.update()
bob.show()

spring.show()
spring.show_line(bob)

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

Exercise 3.13

當彈簧拉長或擠短到超過限定長度時,因為不能再進一步拉長或擠短,而造成吊錘停止運動;所以,這時除了需將彈簧長度調整成限定的長度外,也必須將吊錘的速度歸零。

def constrain_length(self, bob, min_length, max_length):
direction = bob.position - self.anchor
current_length = direction.length()
if current_length <= min_length:
bob.velocity = pygame.Vector2(0, 0)
direction.scale_to_length(min_length)
bob.position = self.anchor + direction
elif current_length >= max_length:
bob.velocity = pygame.Vector2(0, 0)
direction.scale_to_length(max_length)
bob.position = self.anchor + direction

Exercise 3.14

修改Spring類別。除了刪除錨釘相關的程式碼,也要修改connect()方法,讓彈簧兩端都能掛上吊錘。要注意的是,當彈簧兩端連接的都是吊錘時,作用在兩個吊錘上的彈力,大小相等但方向會相反。另外,constrain_length()也需修改,彈簧長度的計算,原先是計算錨釘到吊錘間的距離,現在則是改為計算兩端兩個吊錘間的距離。修改後的Spring類別及主程式如下:

class Spring:
def __init__(self, spring_constant, rest_length):
# 取得顯示畫面
self.screen = pygame.display.get_surface()

# 彈簧常數
self.k = spring_constant

# 彈簧靜止長度
self.rest_length = rest_length

def connect(self, bob1, bob2):
# 計算作用在bob2的作用力,方向是從bob1到bob2
force = bob2.position - bob1.position
current_length = force.length()
x = current_length - self.rest_length # 彈簧形變量
force = -self.k*x*force.normalize()

# 當彈簧兩端連接的都是吊錘時,作用在兩個吊錘的彈力,大小相等但方向相反。
bob2.apply_force(force)
bob1.apply_force(-force)

def constrain_length(self, bob1, bob2, min_length, max_length):
direction = bob2.position - bob1.position
current_length = direction.length()
if current_length <= min_length:
bob1.velocity = pygame.Vector2(0, 0)
bob2.velocity = pygame.Vector2(0, 0)
direction.scale_to_length(min_length)
bob2.position = bob1.position + direction
elif current_length >= max_length:
bob1.velocity = pygame.Vector2(0, 0)
bob2.velocity = pygame.Vector2(0, 0)
direction.scale_to_length(max_length)
bob2.position = bob1.position + direction

def show_line(self, bob1, bob2):
pygame.draw.line(self.screen, (0, 0, 0), bob1.position, bob2.position, 3)


# python version 3.10.9
import math
import random
import sys


import pygame # version 2.3.0

pygame.init()

pygame.display.set_caption("Exercise 3.15")

WHITE = (255, 255, 255)

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

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

# 吊錘及彈簧的數量
n = 5

x, y = width//2, height//2

mass = 12
bobs = [Bob(x+i*15*random.randint(-n, n), y+i*15*random.randint(-n, n), mass) for i in range(n)]

spring_constant = 0.05
rest_length = 100
springs = [Spring(spring_constant, rest_length) for i in range(n)]

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

screen.fill(WHITE)

for spring in springs:
for i in range(n):
bob1 = bobs[i]
bob2 = bobs[(i+1) % n]
spring.connect(bob1, bob2)

for bob in bobs:
bob.update()

for spring in springs:
for i in range(n):
bob1 = bobs[i]
bob2 = bobs[(i+1) % n]
spring.constrain_length(bob1, bob2, 30, 300)
spring.show_line(bob1, bob2)

for bob in bobs:
bob.show()

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

在模擬時,先將所有的吊錘連接上彈簧,讓彈力作用在吊錘上,然後再更新吊錘的狀態。等吊錘狀態更新後,再來檢查及調整彈簧的長度,並畫出彈簧,最後才畫出吊錘。這樣子的處理順序,主要是在避免類似2.6節所討論過的,因為以不同的順序來處理物件間的引力以及更新物件狀態時,所造成的模擬上的差異。

avatar-img
15會員
130內容數
寫點東西自娛娛人
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
ysf的沙龍 的其他內容
在x軸上依序取一些點,然後把這些點以及其對應的sin函數的值所構成的二維座標點畫出來時,就可以看到由這個sin函數所產生的像波一樣的圖案,也就是波型(wave pattern)。不同樣式的波型,可以用來設計生物的軀幹或肢體,也可以用來模擬像水這類柔軟的表面。
藉由設定振幅、頻率、週期等性質,我們可以模擬出真實世界中的振盪現象。其實,用稍微簡單一點的方式來處理,依舊可以得到相同的效果。
這一節談的是振盪(oscillation)。日常生活中,隨處都可見到振盪的現象。例如,彈奏弦樂器時,弦的振動、盪鞦韆時的來回擺動、音叉的振動、單擺的來回擺動、彈簧的振動等。除了這些眼睛看得到的之外,麥克風、交流電、收音機、手機等許許多多的電子產品,也都是利用振盪的原理來運作的。
除了直角座標系統外,極座標(polar coordinate)系統是另一種相當有用的座標系統。
在模擬運動中的物體時,如果物體是圓形,那就不需要考慮旋轉的問題,畢竟不管怎麼轉,圓還是圓,看起來都一樣。但是,如果物體不是圓形而是其他形狀呢?
這一節主要在談三角函數和向量的分量、角度間的關聯性。這個關聯性在向量的應用上,非常重要。
在x軸上依序取一些點,然後把這些點以及其對應的sin函數的值所構成的二維座標點畫出來時,就可以看到由這個sin函數所產生的像波一樣的圖案,也就是波型(wave pattern)。不同樣式的波型,可以用來設計生物的軀幹或肢體,也可以用來模擬像水這類柔軟的表面。
藉由設定振幅、頻率、週期等性質,我們可以模擬出真實世界中的振盪現象。其實,用稍微簡單一點的方式來處理,依舊可以得到相同的效果。
這一節談的是振盪(oscillation)。日常生活中,隨處都可見到振盪的現象。例如,彈奏弦樂器時,弦的振動、盪鞦韆時的來回擺動、音叉的振動、單擺的來回擺動、彈簧的振動等。除了這些眼睛看得到的之外,麥克風、交流電、收音機、手機等許許多多的電子產品,也都是利用振盪的原理來運作的。
除了直角座標系統外,極座標(polar coordinate)系統是另一種相當有用的座標系統。
在模擬運動中的物體時,如果物體是圓形,那就不需要考慮旋轉的問題,畢竟不管怎麼轉,圓還是圓,看起來都一樣。但是,如果物體不是圓形而是其他形狀呢?
這一節主要在談三角函數和向量的分量、角度間的關聯性。這個關聯性在向量的應用上,非常重要。
你可能也想看
Google News 追蹤
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
到目前為止,我們所模擬的萬有引力,是一個物體吸引另一個物體,或者是一個物體吸引多個物體。然而,在真實世界中,每個物體都會互相吸引,所以在這一節中,就來把模擬的情境,擴展成多個物體互相吸引。
Thumbnail
模擬世界是我們寫程式造出來的,我們就是模擬世界的主宰,所以各種作用力要長什麼樣子、要怎麼個作用法,都由我們決定。不過,如果希望這些作用力看起來像真實世界的作用力一樣,那在寫程式的時候,套用這些作用力在真實世界中的物理公式,會是比較省時省力的做法。
在真實世界中有各式各樣的作用力影響著我們,那在模擬世界中呢?要怎麼在本來無一物的模擬世界中,製造出作用力呢?
到目前為止,為了簡化問題,我們都假設物體的質量是1。接下來,我們將移除這個假設,然後將完全符合牛頓第二運動定律的apply_force()方法,整合到Mover這個類別中。
這一節談的是牛頓的三大運動定律,以及力對於物體運動狀態的影響。
介紹以物件導向的方式,以向量來實作物體運動的模擬程式。
Thumbnail
這一節談的是向量的定義,以及如何運用向量來建立模擬物體運動時,關於位置和速度間的關係式。
Thumbnail
1.0 從函數到函算語法 1.2 函數概念小史 1.2.1 中譯的來源 1.2.2 一個速度問題 1.2.3 幾何的方法 1.2.4 微積分的記法 1.2.5弦的振動 1.2.6熱的傳導 二 傅立葉認為他的結果對任一函數皆有效,並將函數定義為 (FF) 在一般情況下,函數
Thumbnail
在中學物理課程裡,大多學生認為“力學”是最難的課題,這是因為它包含有比較多的抽象概念和常常需應用不同的數學技巧。 力學是物理學的一個分支,主要研究能量和力以及它們與物體的平衡、變形或運動的關係。  「“牛頓定律”被認為是力學的最重要理論,我們在這裡簡單介紹它的三條定律。」
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
到目前為止,我們所模擬的萬有引力,是一個物體吸引另一個物體,或者是一個物體吸引多個物體。然而,在真實世界中,每個物體都會互相吸引,所以在這一節中,就來把模擬的情境,擴展成多個物體互相吸引。
Thumbnail
模擬世界是我們寫程式造出來的,我們就是模擬世界的主宰,所以各種作用力要長什麼樣子、要怎麼個作用法,都由我們決定。不過,如果希望這些作用力看起來像真實世界的作用力一樣,那在寫程式的時候,套用這些作用力在真實世界中的物理公式,會是比較省時省力的做法。
在真實世界中有各式各樣的作用力影響著我們,那在模擬世界中呢?要怎麼在本來無一物的模擬世界中,製造出作用力呢?
到目前為止,為了簡化問題,我們都假設物體的質量是1。接下來,我們將移除這個假設,然後將完全符合牛頓第二運動定律的apply_force()方法,整合到Mover這個類別中。
這一節談的是牛頓的三大運動定律,以及力對於物體運動狀態的影響。
介紹以物件導向的方式,以向量來實作物體運動的模擬程式。
Thumbnail
這一節談的是向量的定義,以及如何運用向量來建立模擬物體運動時,關於位置和速度間的關係式。
Thumbnail
1.0 從函數到函算語法 1.2 函數概念小史 1.2.1 中譯的來源 1.2.2 一個速度問題 1.2.3 幾何的方法 1.2.4 微積分的記法 1.2.5弦的振動 1.2.6熱的傳導 二 傅立葉認為他的結果對任一函數皆有效,並將函數定義為 (FF) 在一般情況下,函數
Thumbnail
在中學物理課程裡,大多學生認為“力學”是最難的課題,這是因為它包含有比較多的抽象概念和常常需應用不同的數學技巧。 力學是物理學的一個分支,主要研究能量和力以及它們與物體的平衡、變形或運動的關係。  「“牛頓定律”被認為是力學的最重要理論,我們在這裡簡單介紹它的三條定律。」