The Nature of Code閱讀心得與Python實作:3.2 Angular Motion

更新於 發佈於 閱讀時間約 28 分鐘

角運動(angular motion)指的是物體的旋轉運動。物體進行直線運動時,可以用速度和加速度來描述它的運動。速度指的是單位時間內,物體的位移量;而加速度則是單位時間內,速度的改變量。同樣的做法也可以運用在描述角運動上,我們可以用角速度(angular velocity)和角加速度(angular acceleration)來描述角運動。角速度指的是單位時間內,物體旋轉的角度;而角加速度,則是單位時間內角速度的改變量。

在第1、2章處理位置、速度、加速度時,是使用下列做法:

position += velocity
velocity += acceleration

處理物體的旋轉,也可以用相同的方式:

angle += angular_velocity
angular_velocity += angular_acceleration

不過要注意的是,positionvelocityacceleration是向量,但是angleangular_velocityangular_acceleration則是純量。之所以如此,是因為我們現在是在2D平面上,物體就只會繞著一個軸旋轉;如果是在3D空間中,因為有兩個軸,則它們也會是向量。

另一個需要注意的是,這樣子的做法,是假設模擬的時間間隔delta time,也就是第二章提到過的時間步長,等於顯示1幀畫面的時間。

利用上述的做法,可以把Exercise 3.1改寫成下列Example 3.1的寫法。

Example 3.1: Angular Motion Using transform.rotate()

圖案一開始的時候是靜止不動的,但是在角加速度的作用下,旋轉的速度會越來越快。

# python version 3.10.9
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Example 3.1: Angular Motion Using transform.rotate()")

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)

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

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

baton_length = 200 # 指揮棒長度
radius = 10 # 指揮棒端點球半徑

# 建造用來繪製指揮棒圖案的surface並以白色清空
surface_size = (baton_length, baton_length)
surface = pygame.Surface(surface_size)
surface.fill(WHITE)

# 在surface上繪製指揮棒圖案
endpoint1 = (radius, baton_length//2)
endpoint2 = (baton_length-radius, baton_length//2)
pygame.draw.circle(surface, BLACK, endpoint1, radius)
pygame.draw.circle(surface, BLACK, endpoint2, radius)
pygame.draw.line(surface, BLACK, endpoint1, endpoint2, 5)

# 角度的單位是度
angle = 0
angular_velocity = 0
angular_acceleration = 0.001 # 逆時針旋轉

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

screen.fill(WHITE)

angular_velocity += angular_acceleration
angle += angular_velocity

rotated_surface = pygame.transform.rotate(surface, angle)
rect = rotated_surface.get_rect(center=(WIDTH//2, HEIGHT//2))
screen.blit(rotated_surface, (rect.x, rect.y))

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

Exercise 3.2

# python version 3.10.9
import sys

import pygame # version 2.3.0

pygame.init()

pygame.display.set_caption("Exercise 3.2")

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)

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

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

baton_length = 200 # 指揮棒長度
radius = 10 # 指揮棒端點球半徑

# 建造用來繪製指揮棒圖案的surface並以白色清空
surface_size = (baton_length, baton_length)
surface = pygame.Surface(surface_size)
surface.fill(WHITE)

# 在surface上繪製指揮棒圖案
endpoint1 = (radius, baton_length//2)
endpoint2 = (baton_length-radius, baton_length//2)
pygame.draw.circle(surface, BLACK, endpoint1, radius)
pygame.draw.circle(surface, BLACK, endpoint2, radius)
pygame.draw.line(surface, BLACK, endpoint1, endpoint2, 5)

mass = 2 # 指揮棒質量
Cd = 0.01 # 阻力係數

# 角度的單位是度
angle = 0
angular_velocity = 0
angular_acceleration = 0

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

screen.fill(WHITE)

# 按下滑鼠左鍵時施加給指揮棒的力量
f = 3 if pygame.mouse.get_pressed()[0] else 0

# 計算阻力,並加入避免指揮棒旋轉方向改變的限制條件
direction = 1 if angular_velocity >= 0 else -1
drag_force = -direction*angular_velocity**2*Cd
print(angular_velocity, drag_force)
limit = mass*abs(angular_velocity)
# 阻力最多只能使角速度降至0,無法使其反向
if abs(drag_force) > limit:
drag_force = -direction*limit

angular_acceleration = (drag_force+f)/mass
angular_velocity += angular_acceleration
angle += angular_velocity

rotated_surface = pygame.transform.rotate(surface, angle)
rect = rotated_surface.get_rect(center=(WIDTH/2, HEIGHT/2))
screen.blit(rotated_surface, (rect.x, rect.y))

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

這個旋轉物體的做法也可以加到Mover物件中。要讓Mover物件旋轉,我們可以把角加速度寫死,例如

angular_acceleration = 0.01

不過我們也可以根據環境中作用在物體上的力量,來動態地產生角加速度,這樣子模擬出來的效果,會更有趣一些。

要模擬出讓物體旋轉的物理現象,可以用一個雖然比較取巧,但產生的結果還算符合常理的方法來達到目的。這個方法其實挺簡單的,那就是把角加速度當成是物體加速度向量的函數,例如

 angular_acceleration = acceleration.x

這樣子的設定,會讓物體的加速度向右時,產生逆時針旋轉的角加速度;而當物體的加速度向左時,則會產生順時針旋轉的角加速度。

雖然上述的設定可以達到目的,不過要注意的是,x方向的加速度如果很大,物體可能會因為過大的角加速度,而以看起來不怎麼真實的方式旋轉。要避免這類情況發生,可以把計算方式改成

angular_acceleration = acceleration.x/n  # n是數值

或者幫角速度設一個上限值,都可以達到目的。當然啦,這裡的n和上限值,都必須根據實際的狀況來調整

Example 3.2: Forces with (Arbitrary) Angular Motion

Mover類別的__init__()update()show()需修改。修改後的程式碼為:

def __init__(self, x, y, mass):
# 取得顯示畫面的大小
self.screen = pygame.display.get_surface()
self.width, self.height = self.screen.get_size()

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

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

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

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

# 物件的初始角度、初始角速度、初始角加速度
self.angular_acceleration = 0
self.angular_velocity = 0
self.angle = 0

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

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

self.angular_acceleration = self.acceleration.x/10
self.angular_velocity += self.angular_acceleration
# 限定角速度在一個範圍內
if self.angular_velocity > 0.1:
self.angular_velocity = 0.1
elif self.angular_velocity < -0.1:
self.angular_velocity = -0.1

self.angle += self.angular_velocity

self.acceleration *= 0

def show(self):
circle_center = (self.radius, self.radius)
# 畫出具有透明度的mover
pygame.draw.circle(self.surface, (0, 0, 0, 50), circle_center, self.radius)
# 在圓裡面畫一條直線,這樣才能看得出來是在旋轉
pygame.draw.line(self.surface, (0, 0, 0, 255), circle_center, (2*self.radius, self.radius))

# 旋轉角度的單位需由弳度轉換為度
rotated_surface = pygame.transform.rotate(self.surface, math.degrees(self.angle))
rect_new = rotated_surface.get_rect(center=circle_center)

# 把mover所在的surface貼到最後要顯示的畫面上
x, y = self.position
self.screen.blit(rotated_surface, (x+rect_new.x, y+rect_new.y))

主程式如下:

# python version 3.10.9
import math
import random
import sys

import pygame # version 2.3.0

pygame.init()

pygame.display.set_caption("Example 3.2: Forces with (Arbitrary) Angular Motion")

WHITE = (255, 255, 255)

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

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

movers = [Mover(random.randint(0, WIDTH), random.randint(0, HEIGHT),
random.uniform(1, 2))
for i in range(20)]

attractor = Attractor(0.4, 20, WIDTH/2, HEIGHT/2)

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

screen.fill(WHITE)

attractor.show()

for mover in movers:
force = attractor.attract(mover)
mover.apply_force(force)

mover.update()
mover.show()

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

Exercise 3.3

按滑鼠左鍵可以發射砲彈。

class Projectile:
def __init__(self, x, y, mass):
# 取得顯示畫面的大小
self.screen = pygame.display.get_surface()
self.width, self.height = self.screen.get_size()

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

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

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

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

# 物件的初始角度、初始角速度、初始角加速度
self.angular_acceleration = 0
self.angular_velocity = 0
self.angle = 0

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

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

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

self.angular_acceleration = -self.velocity.x
self.angular_velocity += self.angular_acceleration
# 限定角速度在一個範圍內
if self.angular_velocity > 0.2:
self.angular_velocity = 0.2
elif self.angular_velocity < -0.2:
self.angular_velocity = -0.2

self.angle += self.angular_velocity

self.acceleration *= 0

def show(self):
# 畫出具有透明度的projectile
rect = pygame.Rect(0, 0, self.size, self.size)
pygame.draw.rect(self.surface, (0, 0, 0, 50), rect)

# 旋轉角度的單位需由弳度轉換為度
rotated_surface = pygame.transform.rotate(self.surface, math.degrees(self.angle))
rect_new = rotated_surface.get_rect(center=self.position)

# 把projectile所在的surface貼到最後要顯示的畫面上
self.screen.blit(rotated_surface, (rect_new.x, rect_new.y))

def check_edges(self):
if self.position.x + self.size/2 > self.width:
self.position.x = self.width - self.size/2
self.velocity.x = -0.9*self.velocity.x
elif self.position.x - self.size/2 < 0:
self.position.x = self.size/2
self.velocity.x = -0.9*self.velocity.x

if self.position.y + self.size/2 > self.height:
self.position.y = self.height - self.size/2
self.velocity.y = 0
self.angular_velocity = 0
self.angle = 0
elif self.position.y - self.size/2 < 0:
self.position.y = self.size/2
self.velocity.y = -0.9*self.velocity.y


def friction_force(mu, velocity, N=1):
# 速度小於某個數值時,就設定為0,讓物體停止移動
if velocity.length() >= 0: #0.001:
v_hat = velocity.normalize()
else:
v_hat = pygame.Vector2(0, 0)

return -mu*N*v_hat


# 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.3")

WHITE = (255, 255, 255)

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

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

# 大砲發射砲彈的力道
cannon_force = pygame.Vector2(6, -8)

projectiles = []

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
# 按一下滑鼠左鍵會發射一顆砲彈
if event.button == 1:
new_projectile = Projectile(0, HEIGHT-50, random.uniform(1, 3))
new_projectile.apply_force(cannon_force)
new_projectile.update()
projectiles.append(new_projectile)

screen.fill(WHITE)

for projectile in projectiles:
# 砲彈持續受到重力的作用
gravity = pygame.Vector2(0, 0.1*projectile.mass)
projectile.apply_force(gravity)

# 掉到地上移動時,會有摩擦力
if projectile.position.y+projectile.size >= HEIGHT:
friction = friction_force(0.05, projectile.velocity, N=1)
projectile.apply_force(friction)

projectile.update()
projectile.check_edges()
projectile.show()

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





留言
avatar-img
留言分享你的想法!
avatar-img
ysf的沙龍
18會員
149內容數
寫點東西自娛娛人
ysf的沙龍的其他內容
2024/09/20
這一節要模擬的是擺(pendulum)這個裝置中,構造最簡單、具有理想化性質的單擺(simple pendulum)。
Thumbnail
2024/09/20
這一節要模擬的是擺(pendulum)這個裝置中,構造最簡單、具有理想化性質的單擺(simple pendulum)。
Thumbnail
2024/09/16
我們曾經利用sin函數來模擬彈簧吊錘(bob)的運動,雖然這樣子的做法程式很容易寫,但是卻沒辦法模擬彈簧吊錘受到如風力、重力等環境中其他作用力的影響下,在空間中的運動狀況。要克服這樣子的問題,就不能再倚靠sin函數,而必須改用能夠用來計算彈簧彈力的虎克定律(Hooke's law)。
Thumbnail
2024/09/16
我們曾經利用sin函數來模擬彈簧吊錘(bob)的運動,雖然這樣子的做法程式很容易寫,但是卻沒辦法模擬彈簧吊錘受到如風力、重力等環境中其他作用力的影響下,在空間中的運動狀況。要克服這樣子的問題,就不能再倚靠sin函數,而必須改用能夠用來計算彈簧彈力的虎克定律(Hooke's law)。
Thumbnail
2024/09/13
在x軸上依序取一些點,然後把這些點以及其對應的sin函數的值所構成的二維座標點畫出來時,就可以看到由這個sin函數所產生的像波一樣的圖案,也就是波型(wave pattern)。不同樣式的波型,可以用來設計生物的軀幹或肢體,也可以用來模擬像水這類柔軟的表面。
Thumbnail
2024/09/13
在x軸上依序取一些點,然後把這些點以及其對應的sin函數的值所構成的二維座標點畫出來時,就可以看到由這個sin函數所產生的像波一樣的圖案,也就是波型(wave pattern)。不同樣式的波型,可以用來設計生物的軀幹或肢體,也可以用來模擬像水這類柔軟的表面。
Thumbnail
看更多
你可能也想看
Thumbnail
創作者營運專員/經理(Operations Specialist/Manager)將負責對平台成長及收入至關重要的 Partnership 夥伴創作者開發及營運。你將發揮對知識與內容變現、影響力變現的精準判斷力,找到你心中的潛力新星或有聲量的中大型創作者加入 vocus。
Thumbnail
創作者營運專員/經理(Operations Specialist/Manager)將負責對平台成長及收入至關重要的 Partnership 夥伴創作者開發及營運。你將發揮對知識與內容變現、影響力變現的精準判斷力,找到你心中的潛力新星或有聲量的中大型創作者加入 vocus。
Thumbnail
模擬世界是我們寫程式造出來的,我們就是模擬世界的主宰,所以各種作用力要長什麼樣子、要怎麼個作用法,都由我們決定。不過,如果希望這些作用力看起來像真實世界的作用力一樣,那在寫程式的時候,套用這些作用力在真實世界中的物理公式,會是比較省時省力的做法。
Thumbnail
模擬世界是我們寫程式造出來的,我們就是模擬世界的主宰,所以各種作用力要長什麼樣子、要怎麼個作用法,都由我們決定。不過,如果希望這些作用力看起來像真實世界的作用力一樣,那在寫程式的時候,套用這些作用力在真實世界中的物理公式,會是比較省時省力的做法。
Thumbnail
這一節談的是向量的定義,以及如何運用向量來建立模擬物體運動時,關於位置和速度間的關係式。
Thumbnail
這一節談的是向量的定義,以及如何運用向量來建立模擬物體運動時,關於位置和速度間的關係式。
Thumbnail
「每秒公尺是速度還是速率?」你問 「是速度也是速率。」我答 「那速度與速率有何不同?」你再問 「速度有方向性,速率沒有;速度是向量,速率是純量。」 沒有方向感的人生,是繞圈的速率 從起點又回到原點,有了移動的距離,卻未曾有過位移 找到方向感的人生,有了移動的距離,也有實質的位移 那種每
Thumbnail
「每秒公尺是速度還是速率?」你問 「是速度也是速率。」我答 「那速度與速率有何不同?」你再問 「速度有方向性,速率沒有;速度是向量,速率是純量。」 沒有方向感的人生,是繞圈的速率 從起點又回到原點,有了移動的距離,卻未曾有過位移 找到方向感的人生,有了移動的距離,也有實質的位移 那種每
Thumbnail
這篇介紹如何用加速度取得傾斜角度。 用的是和前篇一樣的<basicMpu6050.h>
Thumbnail
這篇介紹如何用加速度取得傾斜角度。 用的是和前篇一樣的<basicMpu6050.h>
Thumbnail
1.0 從函數到函算語法 1.2 函數概念小史 1.2.1 中譯的來源 1.2.2 一個速度問題 1.2.3 幾何的方法 1.2.4 微積分的記法  二 前面說過,牛頓關心的不是抽象的數學問題,他要解決的是天體運動的問題。他知道,假如他擁有該天體在任何一刻的瞬速數據,他便能夠從質量
Thumbnail
1.0 從函數到函算語法 1.2 函數概念小史 1.2.1 中譯的來源 1.2.2 一個速度問題 1.2.3 幾何的方法 1.2.4 微積分的記法  二 前面說過,牛頓關心的不是抽象的數學問題,他要解決的是天體運動的問題。他知道,假如他擁有該天體在任何一刻的瞬速數據,他便能夠從質量
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News