from vpython import *
scene.width = scene.height = 600
scene.range = 0.6
# A pulse ripples along a rug, demonstrating dynamic changea of shape
# Bruce Sherwood, May 2012
def display_instructions():
s = "<b>Click to halt or run.</b> In VPython programs:\n"
s += " Rotate the camera by dragging with the right mouse button,\n"
s += " or hold down the Ctrl key and drag.\n"
s += " To zoom, drag with the left+right mouse buttons,\n"
s += " or hold down the Alt/Option key and drag,\n"
s += " or use the mouse wheel.\n"
s += "Touch screen: pinch/extend to zoom, swipe or two-finger rotate."
scene.caption = s
display_instructions()
# Construct a square WxH divided into little squares
# There are (w+1)x(h+1) vertices
# Center of rug is at 0,0,0
H = W = 1
w = 1
h = 50
dx = W/w
dy = H/h
# Create a grid of vertex objects covering the rug
verts = []
for y in range(h+1): # from 0 to h inclusive, to include both bottom and top edges
verts.append([])
for x in range(w+1): # from 0 to w inclusive, to include both left and right edges
verts[y].append(vertex(pos=vector(-0.5+x*dx,-0.5+y*dy,0), normal=vector(0,0,1), texpos=vector(x/w,y/h,0), shininess= 0))
# Create quads (equivalent to two triangles) based on the vertex objects just created.
# Note that a particular vertex may be shared by as many as 4 neighboring quads, and
# changing one vertex affects all of the quads that use that vertex.
for y in range(h): # from 0 to h, not including h
for x in range(w): # from 0 to w, not including w
quad(v0=verts[y][x], v1=verts[y][x+1], v2=verts[y+1][x+1], v3=verts[y+1][x], texture=textures.rug)
#scene.waitfor('textures') # wait until the rug texture has been loaded
Lpulse = 0.4 # length of half sine wave
dy_pulse = Lpulse/50
k = pi/(0.6*Lpulse)
A = 0.05
def pulse(z): # return the pulse height and normal
if z < 0.2*Lpulse: return 0
if z > 0.8*Lpulse: return 0
z -= 0.2*Lpulse
return A*sin(k*z)
run = True
def down(ev):
global run
run = not run
scene.bind("mousedown", down)
y = -0.5-Lpulse-dy_pulse # bottom of pulse (starts below rug)
while True:
rate(50)
while not run:
scene.waitfor('redraw')
y += dy_pulse
if y+Lpulse <= -0.5:
continue
if y >= 0.5:
y = -0.5-Lpulse
continue
# Note: floor and ceil are floats in Python 2 but ints in Python 3
start = int(floor((y+0.5)/dy)) # lowest row of vertices in pulse
end = int(ceil((y+0.5+Lpulse)/dy)) # highest row of vertices in pulse
if start < 0:
if end <= 0: continue
start = 0
if end > h:
end = h
yp = -0.5+start*dy
for s in range(start,end):
z0 = pulse(yp-y-dy_pulse)
z1 = pulse(yp-y)
z2 = pulse(yp+dy_pulse-y)
yp += dy # advance to next row
# If slope of a line is dy/dz, normal to the line is in direction < -dz, +dy >
n1y0 = -(z1-z0)
n2y0 = -(z2-z1)
n1y = .5*(n1y0+n2y0) # average adjacent normals to smooth the lighting
n1z = dy
vy = verts[s]
for vx in range(w+1):
vy[vx].pos.z = z1
vy[vx].normal = vector(0,n1y,n1z)