The following version will work with 2.6 and the planned 2.7, as well as with VPython 7. The key point is that changing an object's axis is supposed to set obj.size.x to mag(obj.axis), which is useful in most cases but requires extra code in this situation.
def Eyeball(): # create an eyeball with centre at the origin
eye = sphere( pos = vec( 0, 0, 0), radius = 0.5, color = color.white )
iris = sphere( pos = vec( 0.25, 0, 0), radius = 0.31, color = vec( 0, 0, 0.6 ))
pupil = cylinder( pos = vec( 0, 0, 0), axis = vec( 0.57, 0, 0 ), radius = 0.07, color = color.black)
obj = compound( [eye, iris, pupil] )
return obj
scene.width = 1024
scene.height = 768
# create a head compound object, with invisible components used as location placeholders
Skull = sphere( pos = vec( 0, 0, 0), size = vec( 4, 5, 4), color = vec( 0.9, 0.75, 0.625 ))
Nose = cone( pos = vec( 1.8, -0.7, 0 ), radius = 0.5, axis = vec( -0.1, 2.0, 0 ), color = vec( 0.9, 0.75, 0.625 ))
vRightEye = sphere( pos = vec( 1.6, 0, +0.8), radius = 0.31, color = color.gray(0.2), visible=False) # 'v' for virtual - placeholder only
vLeftEye = sphere( pos = vec( 1.6, 0, -0.8), radius = 0.31, color = color.gray(0.2), visible=False)
Head = compound( [Skull, Nose, vRightEye, vLeftEye] )
Head.angle = Head.length*vec(0,0,1)
ebRight = Eyeball()
ebLeft = Eyeball()
while True:
rate(10)
target = scene.mouse.pos
target.z = 8 # focus on an object that is 4 units in front of the screen
# Move the head slowly towards the mouse. Move one quarter of the distance each step
oldAxis = norm(Head.axis)
newAxis = norm(target - Head.pos)
Head.axis = Head.length*norm((3.0*oldAxis+1.0*newAxis)/4.0)
# Place the visible eyeballs in the same position as the invisible placeholders
world_pos = Head.compound_to_world( vRightEye.pos )
ebRight.pos = world_pos
world_pos = Head.compound_to_world( vLeftEye.pos )
ebLeft.pos = world_pos
# Point the eyeballs at the mouse
ebRight.axis = ebRight.length*norm(target - ebRight.pos)
ebLeft.axis = ebLeft.length*norm(target - ebLeft.pos)