pyvista / pyvista-support

[moved] Please head over to the Discussions tab of the PyVista repository
https://github.com/pyvista/pyvista/discussions
60 stars 4 forks source link

(Dolly) zoom programmatically with parallel projection #207

Closed adeak closed 3 years ago

adeak commented 4 years ago

Description

This is closely related to https://github.com/pyvista/pyvista-support/issues/40.

One can programmatically dolly zoom a scene by manipulating the Plotter's camera_position. However, it seems this is not the case when parallel projection is enabled and we're viewing along one of the axes:

import pyvista as pv
from pyvista import examples

mesh = examples.load_airplane()

plotter = pv.Plotter()
#plotter.enable_parallel_projection()  # uncommenting toggles behaviour
plotter.add_mesh(mesh, color='tan')
plotter.view_xy()
print(plotter.camera_position)
plotter.show(auto_close=False)
print(plotter.camera_position)

The above code will plot the camera position before and after we manually manipulate it. If I interactively zoom the scene to fit the airplane as much as possible, I get something like this for the before and after positions:

[(896.9955291748047, 676.0221252441406, 4018.2204338346514),
 (896.9955291748047, 676.0221252441406, 132.19440269470215),
 (0.0, 1.0, 0.0)]
[(896.9955291748047, 676.0221252441406, 2786.4024698622097),
 (896.9955291748047, 676.0221252441406, 132.19440269470215),
 (0.0, 1.0, 0.0)]

The third item of the first tuple (the z component of the position) is reduced, as expected. However, performing the same thing with the parallel projection line uncommented, the camera position doesn't seem to budge:

[(896.9955291748047, 676.0221252441406, 4018.2204338346514),
 (896.9955291748047, 676.0221252441406, 132.19440269470215),
 (0.0, 1.0, 0.0)]
[(896.9955291748047, 676.0221252441406, 4018.2204338346514),
 (896.9955291748047, 676.0221252441406, 132.19440269470215),
 (0.0, 1.0, 0.0)]

I presume this has to do with the focus being in infinity or something similar.

Is there a way to programmatically dolly zoom with parallel projection? I couldn't find an attribute that was affected by interactively zooming in this case.

banesullivan commented 4 years ago

This is a VTK quirk. See https://discourse.vtk.org/t/zoom-behavior-in-parallel-vs-perspective-projection-camera-position/1148

to emulate zoom in parallel projection, VTK uses a scale factor instead.

So essentially the camera doesn't dolly in parallel projection but rather scales the rendered image.

Here is a demo:

import pyvista as pv
from pyvista import examples

mesh = examples.load_airplane()

a = 4018.2204338346514
b = 609.5770538227225

cpos = [[896.9955291748047, 676.0221252441406, a],
 [896.9955291748047, 676.0221252441406, 132.19440269470215],
 [0.0, 1.0, 0.0]]

plotter = pv.Plotter()
plotter.enable_parallel_projection()
plotter.add_mesh(mesh, color='tan')
plotter.camera_position = cpos
plotter.show(auto_close=False) # image a
cpos[0][2] = b
plotter.camera_position = cpos
plotter.show() # image b
a b
a b

It doesn't matter where the camera is located along the view vector. The rendered image will have the same perspective.

This has to do with the nature of parallel projection where moving closer to an object doesn’t make that object appear bigger as that would be adding perspective and parallel projection is by definition, the lack of perspective. As you mention, this has to do with the focus being infinitely far away

banesullivan commented 4 years ago

So the question still remains, how do we do this scaling/"zooming" programmatically. Honestly I'm not usre off the top of my head... We'll have to look at what the scroll event is doing when the parallel projection is enabled and then mimic that.

Note: already tested plotter.camera.Zoom and it has no effect

adeak commented 4 years ago

Warning: the workaround I'll show below can have all sorts of unintended side-effects. Try to avoid it if possible. I'm only leaving it here for historical reasons.

OK, I believe I've found a workaround thanks to your scale tip. We can use plotter.set_scale with reset_camera=False, but this call with mess with the camera position so we also have to restore the original camera position (but since we know that any point along the camera axis gives the same result this is actually robust):

import pyvista as pv
from pyvista import examples

mesh = examples.load_airplane()

a = 4018.2204338346514
b = 609.5770538227225

cpos = [[896.9955291748047, 676.0221252441406, a],
 [896.9955291748047, 676.0221252441406, 132.19440269470215],
 [0.0, 1.0, 0.0]]

plotter = pv.Plotter()
plotter.enable_parallel_projection()
plotter.add_mesh(mesh, color='tan')
plotter.show(auto_close=False, screenshot='before_scale.png') # before scale
old_pos = plotter.camera_position  # stash this away for safe keeping
plotter.set_scale(2, 2, 2, reset_camera=False)
plotter.show(auto_close=False, screenshot='after_scale.png') # after scale
plotter.camera_position = old_pos  # restore position
plotter.show(screenshot='after_repos.png') # after reposition
before scale after scale after reposition
before_scale after_scale after_repos
banesullivan commented 4 years ago

Oo, I don't think we want to use set_scale.. I believe the scale is done by parallel projection is actually scaling the image, not the scene. set_scale scales the world being rendered and has a lot of known, undesired effects that might not be what you want to deal with.

adeak commented 4 years ago

Yes, I could see that the .scale property didn't change on zooming. I looked at the bounding box and that made sense before and after my scaling workaround, but I can certainly understand that there could be a lot of side-effects (this worked for my use case, however). Want me to edit some caveats into my previous comment?

banesullivan commented 4 years ago

Want me to edit some caveats into my previous comment?

I think its good to leave for others to see the progression. Your choice though

banesullivan commented 4 years ago

This may be a question for VTK discourse. I suspect there is an easy way to do this scaling of the rendered image that we are just overlooking

Keou0007 commented 3 years ago

The parallel "zoom" you are looking for here is called parallel scale. Currently it can only be accessed through the camera object, which is not documented.

plotter.camera.SetParallelScale(1.5)

I've submitted a PR to add a parallel_scale property that would allow you to do a dolly zoom while using parallel projection with something like

for zoom in [0.5, 0.75, 1, 1.25, 1.5]:
    plotter.parallel_scale = zoom
    plotter.show(screenshot=f"image_with_pscale_{zoom}.png", auto_close=False)