回上方

Lecture 10 Keyboard and Mouse Control

建國高中特色選修課程 - 物理現象的程式設計與模擬
作者:賴奕帆
日期:2018/8/28


10-1 Keyboard Control

基本的Keyboard Control範例

請同學先執行第一個範例程式,可以按鍵盤上下左右、i、o可以控制地球的位置,按鍵盤c、r可以控制轉動軸方向,按鍵盤q、w可以控制轉動轉速。

""" 建國中學 Vpython物理模擬 作者: 物理科 賴奕帆老師 日期: 107/08/31 特色課程 Lecture 10 10_1_1_keyboard_control Example.py """ from vpython import * #引用視覺畫套件Vpython angle_v = 0 pos, axis= vector(0, 0, 0), vector(0, 1, 0) scene = canvas(width=1000, height=800, range = 6, background=vec(0.6,0.8,0.8)) ball = sphere(radius = 1.0, texture=textures.earth ) def keyinput(evt): # 呼喚keyboard使用功能 global pos, axis ,angle_v # 定義鍵盤控制參數 move = {'left': vector(-0.1, 0, 0), 'right': vector(0.1, 0, 0), 'up': vector(0, 0.1, 0), 'down': vector(0, -0.1, 0), 'i' : vector(0, 0, -0.1), 'o': vector(0, 0, 0.1)} axel = {'c' : vector(0.00, 0, 0.02), 'r': vector(-0.02, 0, 0)} key_s = {'q' :0.01, 'w' : -0.01} s = evt.key #控制鍵盤事件參數 if s in move : pos = pos + move[s] if s in axel: axis = axis + axel[s] if s in key_s : angle_v = angle_v + key_s[s] scene.bind('keydown', keyinput) # 將鍵盤控制設定綁定於scene視窗 while True: rate(200) ball.rotate(angle=angle_v, axis=axis) ball.pos = pos

Keyboard Control_練習1

下述程式碼執行後會看到一顆黃球在進行圓周運動,目前的圓周半徑R為20,轉速w為1( 每秒鐘1弧度)。
並且可以看到紅色箭頭代表速度向量、白色箭頭代表加速度向量。

""" 建國中學 Vpython物理模擬 作者: 物理科 賴奕帆老師 日期: 107/08/31 特色課程 Lecture 10 10_1_2_keyboard_control Practice_Circle_Motion with velocity and acceleration.py """ from vpython import * #引用視覺畫套件Vpython R = 20 #圓周半徑 B_r = 0.1*R #球的半徑 theta = 0*pi/180 #轉動初始角度 w = 1 #轉動角速度 t=0 dt = 0.001 #時間間隔 0.001 秒 scene = canvas(width=1000, height=800, center = vec(0,0,0), background=vec(0.3,0.4,0.4), forward=vec(0,0,-1),range=60) #設定畫面 ball = sphere(radius = B_r , color=color.yellow, make_trail= True, interval=100) #畫球 ball.pos = vector( R*cos(theta), R*sin(theta), 0) #球初始位置 ball.v = vector( 0, 0, 0) #球初速 v_arrow = arrow(pos=ball.pos,axis=vec(0,0,0),shaftwidth=0.2*B_r ,color = color.red) #速度箭頭 a_arrow = arrow(pos=ball.pos,axis=vec(0,0,0),shaftwidth=0.2*B_r ,color = color.white) #加速度箭頭 while True : rate(1000) #每一秒跑 1000 次 theta = theta + w * dt ball.pos = vector (R*cos(theta), R*sin(theta), 0 ) v_arrow.pos = ball.pos v_arrow.axis = cross(vector(0,0,1),ball.pos).norm() * R * w a_arrow.pos = ball.pos a_arrow.axis = - ball.pos.norm() * R * w * w t = t + dt

課堂作業 10-1

請同學們嘗試合併上述程式,讓鍵盤可以控制圓周運動的圓周半徑R與轉速w,如下圖所示。

課堂作業 10-1 參考解法步驟 :

  • 複製def keyinput(evt)鍵盤控制程式碼:
def keyinput(evt): # 呼喚keyboard使用功能 global pos, axis ,angle_v # 定義鍵盤控制參數 move = {'left': vector(-0.1, 0, 0), 'right': vector(0.1, 0, 0), 'up': vector(0, 0.1, 0), 'down': vector(0, -0.1, 0), 'i' : vector(0, 0, -0.1), 'o': vector(0, 0, 0.1)} axel = {'c' : vector(0.00, 0, 0.02), 'r': vector(-0.02, 0, 0)} key_s = {'q' :0.01, 'w' : -0.01} s = evt.key #控制鍵盤事件參數 if s in move : pos = pos + move[s] if s in axel: axis = axis + axel[s] if s in key_s : angle_v = angle_v + key_s[s] scene.bind('keydown', keyinput) # 將鍵盤控制設定綁定於scene視窗
  • 修改global pos , axis , angle_v,改為R , w。
  • 修改s = evt.key。
  • 注意,R與w都是純量,與原本的key_s相同,不需要加vector。
def keyinput(evt): # keyboard interrupt callback function global R, w # define the global variables that you want to change by this function length = {'q' :0.2, 'w' : -0.2} angle_v = {'a' : 0.1, 's': -0.1} s = evt.key if s in length : R = R + length[s] if s in angle_v: w = w + angle_v[s] scene.bind('keydown', keyinput)

Keyboard Control_練習2

  • 下述程式碼執行後會看到一顆球在進行斜向拋射,並在最高點會自動分裂成兩顆球(質量設定為2:1)。

""" 建國中學 Vpython物理模擬 作者: 物理科 賴奕帆老師 日期: 107/08/31 特色課程 Lecture 10 10_1_3_keyboard_control Practice_Pjojectile motion and boom.py """ from vpython import * #引用視覺畫套件Vpython g=9.8 ; size = 0.5 ; m1 =2 ; m2 =4 ; t = 0 ; dt = 0.001 v0 = 20 ; theta_0 = 60*pi/180 ; s0 = vector( -25.0, size, 0.0) J_vector = norm(vector(1,0,0)) #爆炸衝量方向(1,0,0)為水平爆炸 J_mag = 20 #爆炸衝量強度 scene = canvas(title='拋體運動', width=1000, height=600, center=vec(0,10,0),background=vec(0.6,0.8,0.8))#設定畫面 floor = box(length=60, height=0.01, width=4, texture=textures.wood) #畫地板 ball1 = sphere(radius = size, color=color.red, make_trail = True) #畫球 ball2 = sphere(radius = size, color=color.green, make_trail = True) #畫球 ball3 = sphere(radius = 0.01*size, color=color.black, make_trail = True) #畫球 ball1.pos = s0 ; ball1.v = vector(v0*cos(theta_0), v0*sin(theta_0) , 0.0) #球初速 ball2.pos = s0 ; ball2.v = vector(v0*cos(theta_0), v0*sin(theta_0) , 0.0) #球初速 ball3.pos = s0 ; ball3.v = vector(0, 0 , 0) #球初速 #設定鍵盤按下a,使start > 0 , 開始拋射 start = 0 while ball3.pos.y>=0: #模擬直到球落地 rate(500) #每一秒跑1000次 t = t + dt ball3.pos = (m1*ball1.pos+m2*ball2.pos)/(m1+m2) #兩球的質心 ##設定鍵盤按下a,使start > 0 , 開始拋射 if t >= 0: ball1.pos += ball1.v*dt ball1.v += vector(0,- g*dt,0) ball2.pos += ball2.v*dt ball2.v += vector(0,- g*dt,0) if ball1.v.y < 0 and start < 1: # 注意,此處衝量J_vector只能施加一次爆炸衝擊。 ball1.v += J_vector*J_mag/m1 #球1受到向右的衝量 ball2.v += - J_vector*J_mag/m2 #球2受到向左的衝量 start = start + 1 #使衝量只會受到一次

課堂作業 10-2

請同學們設定鍵盤控制,讓程式執行時,改為可以由鍵盤控制發射炸開的時機。

課堂作業 10-2 參考解法步驟 :

  • 複製def keyinput(evt)鍵盤控制程式碼:
  • 修改global pos , axis , angle_v,改為start。
  • 修改s = evt.key。
def keyinput(evt): # 呼喚keyboard使用功能 global start # 定義鍵盤控制參數 key_s = {'a' :1.0} s = evt.key #控制鍵盤事件參數 if s in key_s : start = start + key_s[s] scene.bind('keydown', keyinput) # 將鍵盤控制設定綁定於scene視窗
  • 修改執行迴圈中球射出的設定,原為"if t >= 0:",改為"if start > 0:"
  • 修改執行迴圈中球爆炸的設定,原為"if ball1.v.y < 0 and start < 1: “,改為"if start > 1.9 and start <2.1”
##設定鍵盤按下a,使start > 0 , 開始拋射 if start > 0: ball1.pos += ball1.v*dt ball1.v += vector(0,- g*dt,0) ball2.pos += ball2.v*dt ball2.v += vector(0,- g*dt,0) if start > 1.9 and start <2.1: # 注意,此處衝量J_vector只能施加一次爆炸衝擊。 ball1.v += J_vector*J_mag/m1 #球1受到向右的衝量 ball2.v += - J_vector*J_mag/m2 #球2受到向左的衝量 start = start + 1 #使衝量只會受到一次

10-2 Mouse Control

基本的Mouse Control範例

  • 執行下述範例,可以用滑鼠按箭頭的頭改變向量的方向與大小。
    用滑鼠按箭頭的尾改變向量的位置。

""" 建國中學 Vpython物理模擬 作者: 物理科 賴奕帆老師 日期: 107/08/31 特色課程 Lecture 10 10_2_1_mouse_control Example.py """ from vpython import * #引用視覺畫套件Vpython scene.range = 8 # fixed size, no autoscaling , 固定螢幕大小 arr = arrow(pos=vec(2,0,0),axis=vec(0,5,0)) by = 1 # touch this close to tail or tip, 滑鼠按下後與尾與箭頭的判斷距離 drag = None # have not selected tail or tip of arrow, 判斷是否選擇了尾與箭頭 drag_pos = vector (0,0,0) new_pos = vector (0,0,0) while True: rate(30) #以每1/30秒的週期執行下列指令 m_ev = scene.waitfor('click mousedown mouseup mousemove') #滑鼠event if m_ev.event == 'mousedown': #如果滑鼠按下左鍵 if mag(arr.pos-m_ev.pos) <= by: #若滑鼠位置與尾端的位置差小於by drag = 'tail' # near tail of arrow, 則判斷正在拖曳tail(尾) elif mag((arr.pos+arr.axis)-m_ev.pos) <= by: #若滑鼠位置與箭頭的位置差小於by drag = 'tip' # near tip of arrow, 則判斷正在拖曳tip(箭頭) drag_pos = m_ev.pos # save press location , 紀錄按下的位置於drag_pos(注意,此時滑鼠左鍵持續按著, 可拖曳) elif m_ev.event == 'mouseup': # released at end of drag, 若滑鼠左鍵放開 drag = None # end dragging (None is False) , 則判斷停者拖曳 if drag: #如果滑鼠正在按下拖曳tail or tip) new_pos = m_ev.pos #把目前滑鼠的位置紀錄於new_pos if new_pos != drag_pos: # if mouse has moved, 如果滑鼠拖曳了, 導致目前滑鼠的位置與之前紀錄的位置不一樣 displace = new_pos - drag_pos # how far , 則計算滑鼠移動了多少距離 drag_pos = new_pos # update drag position, 並更新drag_pos於目前的滑鼠位置 if drag == 'tail': #如果判斷指令為tail(尾) arr.pos = m_ev.pos # displace the tail, 則更新尾巴的位置 if drag == 'tip': #如果判斷指令為tip(箭頭) arr.axis += displace # displace the tip, 則更新向量的長度 print (drag, drag_pos, new_pos)

Mouse Control_範例2

  • 下述程式碼執行後,會看到一個紅球,有個黃色箭頭代表出速度。

  • 按a後會看到球體進行拋體運動,並繪出各個瞬間的曲率半徑。

  • 也可以先用滑鼠改變球的初速度,看看不同拋射狀況下的曲率半徑如何變化。

""" 建國中學 Vpython物理模擬 作者: 物理科 賴奕帆老師 特色課程 Lecture 10 10_2_2 mouse control Practice_Projectile motion_aN and curvature radius 先用滑鼠控制初速度,按a後開始拋射運動 """ from vpython import * #引用視覺畫套件Vpython by = 1 # touch this close to tail or tip, 滑鼠按下後與尾與箭頭的判斷距離 drag = None # have not selected tail or tip of arrow, 判斷是否選擇了尾與箭頭 drag_pos = vector (0,0,0) new_pos = vector (0,0,0) g=10 ; size = 0.5 ; t=0 ; dt = 0.001 ; N =0 ; drag = None scene = canvas(width=1000, height=600, center = vec(20,0,0), background=vec(0.4,0.6,0.6),range=25)#設定畫面 floor = box(pos = vector(50,0,0), length=100, height=0.2, width=4, texture=textures.wood) #畫地板 ball = sphere(radius = size, color=color.red, make_trail= True) #畫球 ball.pos = vector( 0, size, 0.0) ; ball.v = vector(12,16,0) #球初速 v_arrow = arrow(pos=ball.pos,axis=ball.v,shaftwidth=0.3 ,color = color.yellow) #設定鍵盤按下a,開始拋射 start = 0 def keyinput(evt): global start key_s = {'a' :1} s = evt.key if s in key_s : start = start + key_s[s] scene.bind('keydown', keyinput) while ball.pos.y >= size: #模擬直到球落地 即y=球半徑 rate(1000) #每一秒跑1000次 if start <1 : m_ev = scene.waitfor('click mousedown mouseup mousemove keydown') #滑鼠event if m_ev.event == 'mousedown': #如果滑鼠按下左鍵 if mag((v_arrow.pos+v_arrow.axis)-m_ev.pos) <= by: #若滑鼠位置與尾端的位置差小於by drag = 'tip' # near tip of arrow, 則判斷正在拖曳tip(箭頭) drag_pos = m_ev.pos # save press location , 紀錄按下的位置於drag_pos(注意,此時滑鼠左鍵持續按著, 可拖曳) elif m_ev.event == 'mouseup': # released at end of drag, 若滑鼠左鍵放開 drag = None # end dragging (None is False) , 則判斷停者拖曳 elif m_ev.event =='keydown': drag = None if drag: #如果滑鼠正在按下拖曳tail or tip) new_pos = m_ev.pos #把目前滑鼠的位置紀錄於new_pos if new_pos != drag_pos: # if mouse has moved, 如果滑鼠拖曳了, 導致目前滑鼠的位置與之前紀錄的位置不一樣 displace = new_pos - drag_pos # how far , 則計算滑鼠移動了多少距離 drag_pos = new_pos # update drag position, 並更新drag_pos於目前的滑鼠位置 if drag == 'tip': #如果判斷指令為tip(箭頭) v_arrow.axis += displace # displace the tip, 則更新向量的長度 ball.v = v_arrow.axis #設定當鍵盤按下a,開始拋射 if start>=1: t=t+dt ; ball.pos = ball.pos+ball.v*dt ball.v.y += - g*dt ; v_arrow.pos = ball.pos ; v_arrow.axis = ball.v if N==0 or N>=0.4 : #每0.4秒畫一個曲率 v_g_sin_angle = mag(cross(ball.v,vec(0,g,0)))/(mag(ball.v)*g) ring_radius = mag(ball.v)**2/(g*v_g_sin_angle) circle = ring(radius = ring_radius, pos = ball.pos+ring_radius*norm(cross(ball.v,vec(0,0,1))), axis=vec(0,0,1),thickness=0.05, color=color.white ) ball_n = sphere(pos = ball.pos, radius = 0.7*size, color=color.blue) ball_0 = sphere(pos = ball.pos+ring_radius*norm(cross(ball.v,vec(0,0,1))), radius = 0.7*size, color=color.black) r_arrow = arrow(pos=ball.pos+ring_radius*norm(cross(ball.v,vec(0,0,1))),axis=ring_radius*norm(cross(ball.v,vec(0,0,-1))), shaftwidth=0.1, color = color.red) N=0 N=N+dt

Mouse Control_練習1

執行後,會看到地球與許多的紅色箭頭,代表空間中地球的重力場,但還有一個奇怪的藍色箭頭。

""" 建國中學 Vpython物理模擬 作者: 物理科 賴奕帆老師 特色課程 Lecture 10 10_2_3 mouse control Practice_Gravity force.py """ from vpython import * #引用視覺畫套件Vpython G = 6.67 ; M = 6*10**1 ; Re = 10 ; t =0 ; dt = 0.001 def ag(x): return -G*M/(x**2) scene = canvas(width=1000, height=800, center=vec(0,-5,0), background=vec(0.6,0.8,0.8),range=6*Re) earth = sphere(pos=vec(0,0,0), radius=Re, texture=textures.earth) arrowlist = [] for N1 in range(-5,6,1): #X軸 for N2 in range(-5,6,1):#Y軸 arrowlist.append(arrow(pos=vector(N1*Re,N2*Re,0),axis=vector(5,0,0),shaftwidth=1 , color=color.red)) arr = arrow(pos=vector(12.5,12.5,0),axis=vector(5,0,0),shaftwidth=1 , color=color.blue) for N in arrowlist: N_dist = mag(N.pos - earth.pos) N_radiavector = norm(N.pos-earth.pos) if N_dist > 1.2*Re and N_dist < 5*Re: N.axis = ag(N_dist) * N_radiavector * 10 else: N.axis = vector(0,0,0) while True: rate(1000)

課堂作業 10-3

請同學們利用滑鼠控制,能隨意控制藍色箭頭的位置,並讓藍色箭頭能表現出其位置的重力場向量。
`

`

課堂作業 10-3 參考解法步驟 :

  • 在執行迴圈處,讓藍色箭頭arr能在迴圈中即時計算地球引力場強度。
while True: rate(1000) arr_dist = mag(arr.pos - earth.pos) arr_radiavector = norm(arr.pos - earth.pos) if arr_dist > 1.0*Re : arr.axis = ag(arr_dist) * arr_radiavector * 10 else: arr.axis = vector(0,0,0)
  • 複製mouse control中的滑鼠控制程式碼:
    包含基本參數 by , drag , drag_pos 與 new_pos。
by = 1 # touch this close to tail or tip, 滑鼠按下後與尾與箭頭的判斷距離 drag = None # have not selected tail or tip of arrow, 判斷是否選擇了尾與箭頭 drag_pos = vector (0,0,0) new_pos = vector (0,0,0)

和執行迴圈處,所以即時控制滑鼠的部分

m_ev = scene.waitfor('click mousedown mouseup mousemove') #滑鼠event if m_ev.event == 'mousedown': #如果滑鼠按下左鍵 if mag(arr.pos-m_ev.pos) <= by: #若滑鼠位置與尾端的位置差小於by drag = 'tail' # near tail of arrow, 則判斷正在拖曳tail(尾) elif mag((arr.pos+arr.axis)-m_ev.pos) <= by: #若滑鼠位置與箭頭的位置差小於by drag = 'tip' # near tip of arrow, 則判斷正在拖曳tip(箭頭) drag_pos = m_ev.pos # save press location , 紀錄按下的位置於drag_pos(注意,此時滑鼠左鍵持續按著, 可拖曳) elif m_ev.event == 'mouseup': # released at end of drag, 若滑鼠左鍵放開 drag = None # end dragging (None is False) , 則判斷停者拖曳 if drag: #如果滑鼠正在按下拖曳tail or tip) new_pos = m_ev.pos #把目前滑鼠的位置紀錄於new_pos if new_pos != drag_pos: # if mouse has moved, 如果滑鼠拖曳了, 導致目前滑鼠的位置與之前紀錄的位置不一樣 displace = new_pos - drag_pos # how far , 則計算滑鼠移動了多少距離 drag_pos = new_pos # update drag position, 並更新drag_pos於目前的滑鼠位置 if drag == 'tail': #如果判斷指令為tail(尾) arr.pos = m_ev.pos # displace the tail, 則更新尾巴的位置 if drag == 'tip': #如果判斷指令為tip(箭頭) arr.axis += displace # displace the tip, 則更新向量的長度
  • 由於我們這裡使需要控制藍色箭頭的Tail,所以滑鼠控制的部分不需要改變tip長度,因此可以把部分程式碼加上##(alt+3),當然,其實不刪除其實也可以。
## elif mag((arr.pos+arr.axis)-m_ev.pos) <= by: #若滑鼠位置與箭頭的位置差小於by ## drag = 'tip' # near tip of arrow, 則判斷正在拖曳tip(箭頭) ## drag_pos = m_ev.pos # save press location , 紀錄按下的位置於drag_pos(注意,此時滑鼠左鍵持續按著, 可拖曳) ## if drag == 'tip': #如果判斷指令為tip(箭頭) ## arr.axis += displace # displace the tip, 則更新向量的長度

Scene bind mouse

這堂課的最後,介紹一個有趣的滑鼠控制功能。
執行下述程式後,畫面中會出現一個藍球。

但若用滑鼠在螢幕中按下,藍球的顏色會改變,且滑鼠按下的位置也會出現一顆綠球。

from vpython import * s = sphere(color=color.cyan) def change(ev): if s.color.equals(color.cyan): s.color = color.red else: s.color = color.cyan sphere(pos=ev.pos, radius=0.2, color=color.green) scene.bind('click', change)

這功能有什麼用呢,聰明的同學應該已經想到了,在Lecture9中,曾經有過電力線的討論,若我們利用這個功能,就可以隨意在電力線中加入隨意的粒子,看看它們在場中會如何的運動。

最後就不當作業了,直接當範例給大家參考吧,大家辛苦了,希望都能持續精進,加油吧。

""" 建國中學 Vpython物理模擬 作者: 物理科 賴奕帆老師 日期: 107/08/31 特色課程 Lecture 10 10_2_4_mouse_control Example_Electric field simulation.py """ from vpython import * #引用視覺畫套件Vpython k = 9*10**9 size = 0.4 # charge size t = 0 ; dt = 0.001 b_N = 20 Q1_charge = 1*10**(-5) #Q1電量 Q1_position = vector(-2, 0, 0) #Q1位置 Q2_charge = -1*10**(-5) #Q2電量 Q2_position = vector(2, 0, 0) #Q2位置 q_charge = 1 * 10 **(-7) q_position = vector (1 , 1 , 0) q_m = 10**(-3) q_v = vector (0 , 0 , 0) scene = canvas(title='dipole', height=600, width=1000, range=5.0, auto_scale=False, background=vec(1.0,1.0,1.0)) Q1 = sphere(pos = Q1_position , radius = size , color = color.blue) Q2 = sphere(pos = Q2_position , radius = size , color = color.red) field_ball_1=[] for N in range(0,b_N,1):#build field ball from wall field_ball_1.append(sphere(pos=vector(size*cos(2*pi*N/b_N), size*sin(2*pi*N/b_N),0)+Q1_position, radius=0.01, color=vec(1,1,0), make_trail=True, v=vector(0,0,0))) field_ball_2=[] for N in range(0,b_N,1):#build field ball from wall field_ball_2.append(sphere(pos=vector(size*cos(2*pi*N/b_N), size*sin(2*pi*N/b_N),0)+Q2_position, radius=0.01, color=vec(0.8,0.8,0.3), make_trail=True, v=vector(0,0,0))) def Force_E(r, q):#force of field r1 = r - Q1_position r2 = r - Q2_position return k*q*Q1_charge*r1.norm()/(r1.mag*r1.mag)+k*q*Q2_charge*r2.norm()/(r2.mag*r2.mag) q = [] def make_q_charge(evt): loc = evt.pos print ("click at ", loc) q.append(sphere(pos=loc, radius=0.2*size, color=color.green, make_trail=True, v=vector(0,0,0))) scene.bind('mousedown', make_q_charge) while True: rate(1000) for N in field_ball_1: N.v = Force_E(N.pos, 1.0).norm() N.pos += N.v*dt for N in field_ball_2: N.v = Force_E(N.pos, -1.0).norm() N.pos += N.v*dt for N in q: if mag(N.pos-Q1_position)>=size and mag(N.pos-Q2_position)>=size : N.v = N.v + Force_E(N.pos, q_charge)/q_m *dt N.pos = N.pos+N.v*dt else : N.pos = N.pos

本單元課程自2018.7.1日起已被瀏覽 184