jupyter-widgets / pythreejs

A Jupyter - Three.js bridge
https://pythreejs.readthedocs.io
Other
942 stars 188 forks source link

Resetting camera position doesn't appear to work well #188

Closed JuanCab closed 6 years ago

JuanCab commented 6 years ago

So, I wanted to offer my students unused to 3D widgets the ability to have a push button that would reset the view. However when I do the following

    renderer2.camera.position=[0, 0, 3*xmax]
    renderer2.camera.up=[0, 1, 0]

the scene disappears. It is clear if I then interact with the scene, it appears having reset position and view. Am I missing something to trigger a scene re-rendering after changing the camera? This isn't an animation, just a simple 'reset' of position.

vidartf commented 6 years ago

There are two points to take into account when "resetting" a camera:

  1. When setting position and up vector, the rotation of the camera doesn't necessarily change. The best way to achieve what you want is likely to use camera.lookat([x, y, z]).

  2. The "standard way" of setting up a renderer normally adds an OrbitControls. It keeps its own reference to an orbit point. While the camera only looks in a specific direction, the orbit control orbits around a 3D point. Directly modifying the camera does not update the orbit. This is what is causing your rendering to start "working again" after interacting with the scene. You can programmatically set the orbit target with orbit.target = (x, y, z), but this is currently not synced back from the front-end (this should ideally be fixed). For this reason, you might need to do a cycle (orbit.target = [0, 0, 0]; orbit.target = [x, y, z] although that might fail for x = y = z = 0).

JuanCab commented 6 years ago

Sorry my question/issue wasn't clearly stated...

I am using OrbitControls, so using camera.lookat([x, y, z]) doesn't achieve what I want. The idea was to allow someone to move around the origin of the view of a simulation, but then allow them to reset a view to an "overhead view" in an OrbitControls guided view. So I was moving the camera back to being over the seen and looking down on it. That works in that the camera moves, but then the renderer doesn't re-display the scene until the user interacts with it.

The actual code defining my camera and controller is

import pythreejs as p3j

# Lots of code clipped out here as not critical to understanding the problem

starcam = p3j.PerspectiveCamera(position=[0, 0, 3*xmax], up=[0, 1, 0],
                      children=[p3j.DirectionalLight(color='white', 
                                                 position=[1.5*xmax, 1.5*xmax, 1.5*xmax], 
                                                 intensity=1)])
controller = p3j.OrbitControls(controlling=starcam, enableRotate=True, enableZoom=False, 
                               minPolarAngle=0, maxPolarAngle=np.pi, target = [0, 0, 0])
renderer2 = p3j.Renderer(camera=starcam, 
                    scene=scene2, 
                    controls=[controller],
                    width=view_width, height=view_height)

The behavior you describe in your second point is the problem, that when I reset the starcam camera position and up the rendering starts to work only after an interaction. I would be nice it there was some way to programmatically refresh the renderer after an unexpected change like this.

JuanCab commented 6 years ago

I did think of trying to do this with a animation of a position track, but AnimationAction doesn't appear to have a way to automatically start when called or to not display the controls. I'd like to use a button to "reset to overhead".

vidartf commented 6 years ago

There is actually a reset() function on the JS side of OrbitController. It seems like it might make sense to expose that to the kernel side!

vidartf commented 6 years ago

For now, you can call it directly with controller.exec_three_obj_method('reset').

vidartf commented 6 years ago

It should probably also get a method to "sync" to the camera after another source changes the camera, but that will need some careful consideration of how to best achieve it.