projecting a click on a sphere

67 views
Skip to first unread message

Matěj Ryston

unread,
Jun 20, 2017, 4:21:37 PM6/20/17
to VPython-users
Hello,

I have the following problem. I am working on a program to show parallel transport on curved surfaces. I started with a sphere. I would like to be able to create a point on a big sphere with the click of a mouse. So far I have this:

from visual import *

R = 10
points = []
point = None
click_pos = None

scene.range = 15
scene.forward = (cos(10*pi/9)*sin(2*pi/3),sin(10*pi/9)*sin(2*pi/3),cos(2*pi/3))
scene.up = (0,0,-1)

def click(evt):
    global big_sphere, point
    global click_pos
    if evt.pick == big_sphere:
        click_pos = evt.pickpos
        theta = acos(click_pos.z/sqrt(click_pos.x**2+click_pos.y**2+click_pos.z**2))
        phi = atan2(click_pos.y,click_pos.x)
        point = sphere(pos = (R*cos(phi)*sin(theta),R*sin(phi)*sin(theta),R*cos(theta)), radius = 0.1, color = color.blue, make_trail = True)
        points.append(point) 


scene.bind('mousedown', click)

big_sphere = sphere(pos = (0,0,0), radius = R, color = color.cyan)

while(True):
    rate(100)

In the click() function I don't use the click_pos directly because my points were almost always created under the surface of the sphere. The problem is that the evt.pick doesn't register everywhere on the sphere. At some places on the sphere it just doesn't registered as clicking on the sphere (I verified this by making the program print a message whenever it did) and in some places the point is created on the opposite side of the sphere, as if the click ignores the closer surface and goes through the sphere to the other side. The problem seems to occure more where there is less light on the sphere but it might be a coincidence. Is there a better way to project a click on the surface of a sphere? Am I using the evt.pick feature in a wrong way? I drew inspiration from the documentation, so I don't think that is the case.

Anyway, thank you to anyone who would be willing to address this issue.

Matěj Ryston

Bruce Sherwood

unread,
Jun 20, 2017, 9:35:08 PM6/20/17
to VPython-users
I am unable to get your program to fail, no matter how I rotate the camera. Exactly what do I have to do to make it fail?

I'll comment that you don't need the while loop. When you bind the click function to mousedown events, that's sufficient.

Also, you don't need to declare point = None nor click_pos = None, because those variables are only used inside the click function. 

Nor do you need the global statements for point or click_pos (since they are only used in the click function), nor do you need "global big_sphere", just as you don't need "global points", because mutable objects such as points (a list) and sphere are already in a sense global.

Matěj Ryston

unread,
Jun 21, 2017, 8:00:15 AM6/21/17
to VPython-users
In my case, the program doesn't generate the blue points (or generates them at the opposite site) when I rotate the camera and try it on the "northern hemisphere", near the top in the Z direction. Also, I just tried copying the program into Glowscript (plus tweaking the vector formalism etc.) and it doesn't display the points at all, not sure why. 

Regarding the unnecesary declarations, they are in place for further functions once I resolve this issue. 

Anyway, thank you for spending time on my problem. Let me know if there is anything else I can do to clarify the isssue.

Matěj Ryston

Wayne

unread,
Jun 21, 2017, 10:30:10 AM6/21/17
to VPython-users
I tried running it in a current VPython notebook running on beta.mybinder (launchable from the "launch binder" button at the bottom of the page here) and I don't see it working either when I click.

Below is the original code adjusted to run there. I tried adding a print statement too but maybe not in the right place?

```
from vpython import *
#from visual import *
from math import pi

R = 10
points = []
point = None
click_pos = None

scene.range = 15
scene.forward = vec(cos(10*pi/9)*sin(2*pi/3),sin(10*pi/9)*sin(2*pi/3),cos(2*pi/3))
scene.up = vec(0,0,-1)

def click(evt):
    global big_sphere, point
    global click_pos
    if evt.pick == big_sphere:
        click_pos = evt.pickpos
        theta = acos(click_pos.z/sqrt(click_pos.x**2+click_pos.y**2+click_pos.z**2))
        phi = atan2(click_pos.y,click_pos.x)
        point = sphere(pos = vec(R*cos(phi)*sin(theta),R*sin(phi)*sin(theta),R*cos(theta)), radius = 0.1, color = color.blue, make_trail = True)
        points.append(point) 
        print ("picked") #correct place for this?


scene.bind('mousedown', click)

big_sphere = sphere(pos = vec(0,0,0), radius = R, color = color.cyan)

while(True):
    rate(100)
```

Bruce Sherwood

unread,
Jun 21, 2017, 10:48:58 AM6/21/17
to VPython-users
I see the effect. I make the window very large. I don't rotate the camera. I click at the equator, and then march upward, clicking as I go. I note that as I get farther from the equator, the small blue sphere is farther and farther away from where I click with the mouse. When I get very near the pole, evt.pick is None despite clicking the sphere.

So there are two bugs in Classic VPython. One is that near the edge of a sphere it fails to detect that the mouse has touched the sphere (failure of evt.pick), and the other is that the farther you are from the center of the sphere, the more incorrect is the position given by evt.pickpos.

VPython 7 does not have a pickpos feature, nor does an event contain evt.pick; you would need to do some geometry using scene.mouse.ray and scene.camera.pos. Your function would look something like this:

def click(evt):
    hit = scene.mouse.pick
    if hit is not None:
        # use scene.mouse.ray and scene.camera.pos (and big_sphere.pos) to position the blue sphere

Here you will find a diagram of the geometry of the camera: 


I'm curious about "(launchable from the "launch binder" button at the bottom of the page here)". Where is "here"?

Bruce Sherwood

unread,
Jun 21, 2017, 10:50:58 AM6/21/17
to VPython-users
I should add that VPython 7 and GlowScript VPython execute the equivalent of "from math import *" and also import clock(), random(), and arange().

Wayne

unread,
Jun 21, 2017, 11:28:33 AM6/21/17
to VPython-users
Hi Bruce,
Here is just my fork of your vpython-jupyter repo. I have added the Seaborn module for plotting things I use. I know it is currently working well with the beta version of my binder.org and so I am using that as the link to the Binder version. (I just checked after writing that and yours seems to be running now too when you press `launch binder` at your repo.)

Thanks for the information about VPython 7. That explains that.

Thanks,
Wayne

Bruce Sherwood

unread,
Jun 21, 2017, 12:11:30 PM6/21/17
to VPython-users
Here's a program that works:

from vpython import *
R = 10
points = []
point = None
click_pos = None

scene.range = 15
scene.forward = vec(cos(10*pi/9)*sin(2*pi/3),sin(10*pi/9)*sin(2*pi/3),cos(2*pi/3))
scene.up = vec(0,0,-1)

def click(evt):
    global big_sphere, point
    global click_pos
    hit = scene.mouse.pick
    if hit is not None:
        L = big_sphere.pos-scene.camera.pos
        ray = scene.mouse.ray
        axis = cross(ray,L)
        a = diff_angle(ray,L)
        if a == 0:
            pos = big_sphere.pos-R*norm(L)
        else:
            b =  acos(sin(a)*mag(L)/R)
            theta = pi/2-(a+b)
            pos = big_sphere.pos - R*norm(L.rotate(angle=theta, axis=axis))
        point = sphere(pos = pos, radius = 0.1, color = color.blue, make_trail = True)
        points.append(point) 

Bruce Sherwood

unread,
Jun 21, 2017, 5:03:57 PM6/21/17
to VPython-users
I notice that my if statement isn't needed. What's in the else branch will work correctly when a is zero.

Here's what the angles b and theta are: Draw scene.mouse.ray from the camera through the sphere. Draw a perpendicular P from the center of the sphere to that ray. Consider the triangle defined by the location of the camera, the center of the sphere, and the point where the perpendicular P intersects the ray. With R the radius of the sphere and L the vector from camera to the center of the sphere, we have mag(L)*sin(a) = R*cos(b), where b goes from the perpendicular P to the line B from the center of the sphere to where the ray intersects the front surface of the sphere.

We also have from the triangle defined above that a+b+theta = pi/2, where theta is the angle between L and B. Consider the vector -R*norm(L), which goes from the center of the sphere to the point where L intersects the font surface of the sphere. Rotate that vector around the axis that is cross(ray,L), add it to the center of the sphere, and you get the intersection of the ray with the front surface of the sphere.

Matěj Ryston

unread,
Jun 22, 2017, 7:15:44 AM6/22/17
to VPython-users
I was going to look for the geometric solution today but you beat me to it and I have to say, your solution is brilliant. You have my thanks, Mr. Sherwood. Your solution has been not only helpful but also educational for my future VPython projects.

Bruce Sherwood

unread,
Jun 22, 2017, 9:17:47 AM6/22/17
to VPython-users
You are very welcome. It was fun figuring this out. 

Advertisement for VPython: Note how powerful it is to have a vector class that permits adding and subtracting vectors, taking cross and dot products, having vector positions for camera and object, and scene.mouse.ray. In the use of VPython in introductory physics courses, VPython is the first environment we've had for students to have the experience of using vectors as powerful entities. Previously, vectors were treated only in terms of components, typically expressed in terms of sines and cosines, which represents only a pale shadow of the power of real vectors.
Reply all
Reply to author
Forward
0 new messages