In [ ]:
from vpython import *
import time

# Bruce Sherwood

# A surreal scene that illustrates many of the features of GlowScript

# Add instructions below the display
s = "<b>Fly through the scene:</b> With the mouse button down near the center of the canvas,<br><br>"
s += "    move the mouse or your finger above or below the center of the scene to move forward or backward;<br><br>"
s += "    move the mouse or your finger right or left to turn your direction of motion.<br><br>"
s += "(Normal VPython rotate and zoom are turned off in this program.)"
scene.caption = s

ycenter = 2
scene.width = 800
scene.height = 400
scene.range = 12
scene.center = vector(0,ycenter,0)
scene.userspin = False
scene.userzoom = False
scene.background = color.gray(0.5)
scene.ambient = color.gray(0.4)

scene.title = "<b>Surreal Stonehenge</b>"

def hourminute():
    now = time.localtime(time.time())
    hour = now[3] % 12
    minute = now[4]
    return [hour, minute]

class analog_clock:
    def __init__(self, pos, radius, axis):
        self.pos = pos
        self.axis = axis
        self.radius = radius
        self.spheres = []
        self.hour = 0
        self.minute = -1
        for n in range(12):
            sp = sphere(pos=pos+(radius*scene.up).rotate(angle=-2*pi*n/12, axis=axis),
                    size=(2*radius/20)*vector(1,1,1),
                    color=color.hsv_to_rgb(vector(n/12,1,1)) )
            self.spheres.append(sp)
        self.hand = arrow(pos=self.pos, axis=0.95*radius*scene.up,
                    shaftwidth=radius/10, color=color.cyan)
        self.update()
        
    def update(self):
        hour, minute = hourminute()
        hour = hour % 12
        if self.hour == hour and self.minute == minute: return
        self.hand.axis = (0.95*self.radius*scene.up).rotate(
                    axis=vector(0,0,1), angle=-2*pi*minute/60)
        self.spheres[self.hour].size = (2*self.radius/20)*vector(1,1,1)
        self.spheres[hour].size = (2*self.radius/10)*vector(1,1,1)
        self.hour = hour
        self.minute = minute

grey = color.gray(0.8)
Nslabs = 8
R = 10
w = 5
d = 0.5
h = 5
photocenter = 0.15*w

# The floor, central post, and ball atop the post
floor = box(pos=vector(0,-0.1,0),size=vector(.2,24,24), axis=vector(0,1,0), texture=textures.wood)
pole= cylinder(pos=vector(0,0,0),axis=vector(0,1,0), size=vector(h,0.4,0.4), color=color.red)
sphere(pos=vector(0,h,0), size=vector(1,1,1), color=color.red)

# Set up the gray slabs, including a portal
for i in range(Nslabs):
    theta = i*2*pi/Nslabs
    c = cos(theta)
    s = sin(theta)
    xc = R*c
    zc = R*s
    if i == 2: # Make a portal
        box(pos=vector(-3.*w/8.,0.75*h/2.,R),
            size=vector(0.5*w/2,0.75*h,d), color=grey)
        box(pos=vector(3.*w/8.0,0.75*h/2.,R),
            size=vector(0.5*w/2,0.75*h,d), color=grey)
        box(pos=vector(0,0.85*h,R),
            size=vector(w,0.3*h,d), color=grey)
    else:
        slab = box(pos=vector(R*c, h/2., R*s), axis=vector(c,0,s),
                   size=vector(d,h,w), color=grey)
        if i != 6:
            T = textures.flower
            if (i == 7 or i == 4): T = textures.rug
            box(pos=slab.pos,
                size=vec(1.1*d,0.9*4*photocenter,0.9*4*photocenter), axis=vec(c,0,s),
                    texture=T)

# Decorate back slab with a gold box and a clock
box(pos=vector(0,h/2.,-R+d/2+0.1), size=vector(w/2.,w/2.,0.2), texture=textures.wood)
clock = analog_clock(vector(0,h/2.,-R+d/2+0.2+0.2*h/10), 0.2*w, vector(0,0,1))

# Draw guy wires from the top of the central post
Nwires = 32
for i in range(Nwires):
    theta = i*2*pi/Nwires
    L = vector(R * cos(theta), -h - 0.1, R * sin(theta))
    cylinder(pos=vector(0,h,0), axis=L, size=vector(mag(L),.04,.04), color=vector(1,0.7,0))

# Display a pyramid
pyramid(pos=vector(-4,0,-5), size=vector(2,2,2), axis=vector(0,3,0), color=vector(0,.5,0), texture=textures.rough)

# Display smoke rings rising out of a black tube
smoke = []
Nrings = 20
x0, y0, z0 = -5, 1.5, -2
r0 = 0.075
spacing = 0.2
thick = r0/3
dr = 0.0075
dthick = thick/Nrings
gray = 1
cylinder(pos=vector(x0,0,z0), axis=vector(0,y0+r0,0), radius=1.5*(r0+thick), color=color.black)

# Create the smoke rings
for i in range(Nrings):
  smoke.append(ring(pos=vector(x0,y0+spacing*i,z0), axis=vector(0,1,0),
                radius=r0+dr*i, thickness=thick-dthick*i))
y = 0
dy = spacing/20
top = Nrings-1

# Log rolls back and forth between two stops
rlog = 1
wide = 4
zpos = 2
zface = 5
tlogend = 0.2
v0 = 0.3
v = v0
omega = -v0 / rlog
theta = 0
dt = 0.1
tstop = 0.3
logcyl = cylinder(pos=vector(-wide, rlog, zpos), size=vector(zface - zpos, 2, 2),
    axis=vector(0, 0, 1), texture=textures.granite)
leftstop = box(pos=vector(-wide-rlog-tstop/2,0.6*rlog,(zpos+zface)/2),
    size=vector(tstop, 1.2*rlog, (zface-zpos)), color=color.red, emissive=True)
rightstop = box(pos=vector(wide+rlog+tstop/2,0.6*rlog,(zpos+zface)/2),
    size=vector(tstop, 1.2*rlog, (zface-zpos)), color=color.red, emissive=True)

# Run a ball up and down the pole
y1 = 0.2*h
y2 = 0.7*h
rball = 0.4
Dband = 1.3 * pole.size.y
cylinder(pos=vector(0,y1-0.9*rball,0), axis=vector(0,1,0), size=vector(0.1,Dband,Dband), color=color.green)
cylinder(pos=vector(0,y2+0.9*rball,0), axis=vector(0,1,0), size=vector(0.1,Dband,Dband), color=color.green)
vball0 = 0.3*v0
vball = vball0
ballangle = 0.05*pi
ball = []
ball.append(sphere(pos=vector(0,0,0), size=2*rball*vector(1,1,1), color=color.blue))
for nn in range(4):
    cc = cone(pos=vector(0,0,0)+vector(0.8*rball,0,0), axis=vector(3*rball,0,0), size=rball*vector(3,1,1), color=color.yellow)
    cc.rotate(angle=0.5*nn*pi, axis=vector(0,1,0), origin=vector(0,0,0))
    ball.append(cc)
ball = compound(ball)
ball.pos = vector(0,y1,0)

# A table with a mass-spring object sliding on it
table = cone(pos=vector(0.4*R, h/4, -.3*R), size=vector(h/4, 0.6 * R, 0.6 * R), 
  axis=vector(0, -1, 0), texture=dict(file=textures.wood_old, turn=1))
tabletop = table.pos
rspring = 0.02 * h
Lspring = .15 * R
Lspring0 = .1 * R
hmass = 4 * rspring
post = cylinder(pos=tabletop, axis=vector(0, 1, 0), size=vector(2 * hmass, .4, .4), color=color.gray(.6))
spring = helix(pos=post.pos + vector(0, hmass/2, 0), size=vector(Lspring, 2 * rspring, 2 * rspring),
               color=color.orange, thickness=rspring)
mass = cylinder(pos=post.pos + vector(Lspring, 0, 0), axis=vector(0, 1, 0),
                size=vector(hmass, .04 * R, .04 * R), color=color.orange)
mass.p = vector(10, 0, 5)
mass.m = 1
kspring = 200
deltat = .01

# Display an ellipsoid
Rcloud = 0.8*R
omegacloud =3*v0/Rcloud
cloud = sphere(pos=vector(0,0.7*h,-Rcloud), size=vector(5,2,2),
                  color=color.green, opacity=0.3)

rhairs = 0.025 # half-length of crosshairs
dhairs = 2 # how far away the crosshairs are
maxcosine = dhairs/sqrt(rhairs**2+dhairs**2) # if ray inside crosshairs, don't move
haircolor = color.black
roam = 0

# Decorate the front entrance slab (GlowScript currently lacks a 3D text object)
#text(pos=(0, 0.77*h, R+d/2), text="No Exit", color=color.yellow,
#           depth=0.3, height=0.7, align="center")

# Display extruded text
##text( pos=(2.0,-0.3*sin(pi/10),-2.0), text='A', height=2.0, depth=0.5,
##     color=(0,0,1.0), up=(0,cos(pi/3.4),-sin(pi/3.4)), axis=(0.5,0,1))
##text( pos=(3.2,0,-2), text='B', height=2.0, depth=0.5,
##     color=(1.0,1.0,0), axis=(1,0,0.3))
##text( pos=(5.0,-0.6*sin(pi/18),-1.4), text='C', height=2.0, depth=0.5,
##     color=(1.0,0,1.0), axis=(1,0,0.6), up=(0,cos(pi/8),sin(pi/8)) )

scene.visible = False
#scene.waitfor("textures")
scene.visible = True

roam = False

def setroam(evt):
    global roam
    roam = not roam
    
scene.bind("mousedown mouseup", setroam)

while True:
    rate(30)

    # If in roaming mode, change camera position and direction according to mouse position
    if roam:
        ray = scene.mouse.ray
        if abs(ray.dot(scene.forward)) < maxcosine: # do something only if outside crosshairs
            newray = norm(vector(ray.x, 0, ray.z))
            angle = asin(scene.forward.cross(newray).dot(scene.up))
            scene.camera.rotate(angle=angle/30, axis=scene.up)
            scene.camera.pos = scene.camera.pos + (ray.y/4)*norm(scene.camera.axis)

    # Roll the log
    theta = theta + omega*dt
    logcyl.pos.x = logcyl.pos.x+v*dt
    logcyl.rotate(angle=omega*dt, axis=vector(0,0,1))
    if logcyl.pos.x >= wide:
        v = -v0
        omega = -v/rlog
        if rightstop.color.equals(color.red):
            rightstop.color = color.cyan
        else:
            rightstop.color = color.red
    if logcyl.pos.x <= -wide:
        v = +v0
        omega = -v/rlog
        if leftstop.color.equals(color.red):
            leftstop.color = color.cyan
        else:
            leftstop.color = color.red

    # Move the cloud
    cloud.rotate(angle=omegacloud*dt, origin=vector(0,0,0), axis=vector(0,1,0))

    # Run the ball up and down
    ball.pos.y = ball.pos.y+vball*dt
    ball.rotate(angle=ballangle, axis=vector(0,1,0), origin=vector(0,0,0))
    if ball.pos.y >= y2:
        vball = -vball0
        ballangle = -ballangle
    if ball.pos.y <= y1:
        vball = +vball0
        ballangle = -ballangle

    # Move the smoke rings
    for i in range(Nrings):
        # Raise the smoke rings
        smoke[i].pos = smoke[i].pos+vector(0,dy,0)
        smoke[i].radius = smoke[i].radius+(dr/spacing)*dy
        smoke[i].thickness = smoke[i].thickness - (dthick/spacing)*dy
    y = y+dy
    if y >= spacing:
        # Move top ring to the bottom
        y = 0
        smoke[top].pos = vector(x0, y0, z0)
        smoke[top].radius = r0
        smoke[top].thickness = thick
        top = top-1
    if top < 0:
        top = Nrings-1
        
    # Update the mass-spring motion
    F = -kspring * (spring.size.x - Lspring0) * spring.axis.norm()
    mass.p = mass.p + F * deltat
    mass.pos = mass.pos + (mass.p / mass.m) * deltat
    spring.axis = mass.pos + vector(0, hmass / 2, 0) - spring.pos

    # Update the analog clock on the back slab
    clock.update()



In [ ]:


In [ ]: