Opened 6 years ago

Last modified 6 years ago

#2552 assigned enhancement

Enhancement: mousemode to Z-shift CofR

Reported by: Tristan Croll Owned by: Tom Goddard
Priority: moderate Milestone:
Component: Graphics Version:
Keywords: Cc:
Blocked By: Blocking:
Notify when closed: Platform: all
Project: ChimeraX

Description

I've just added this to Clipper, but it might make sense as part of ChimeraX in general to go with the "cofr centerOfView" mode? Will attach a quick movie to demonstrate.

class Z_Shift_CofR(MouseMode):
    def __init__(self, session):
        self._step_multiplier = 1
        super().__init__(session)
        from chimerax.core.graphics import Drawing
        d = self._drawing = Drawing('Depth Indicator')
        d.set_geometry(*self._drawing_geometry())
        d.display = False
        self.view.drawing.add_drawing(d)

    def cleanup(self):
        self.view.drawing.remove_drawing(self._drawing)

    def mouse_down(self, event):
        MouseMode.mouse_down(self, event)
        self._set_drawing_position()
        self._set_drawing_color()
        self._drawing.display=True

    def mouse_drag(self, event):
        dx, dy = self.mouse_motion(event)
        self.move_camera_and_cofr(-3*self.pixel_size()*dy)
        self._set_drawing_position()

    def mouse_up(self, event):
        self._drawing.display=False
        MouseMode.mouse_up(self, event)

    def wheel(self, event):
        d = event.wheel_value()
        psize = self.pixel_size()
        self.move_camera_and_cofr(-100*d*psize)

    def move_camera_and_cofr(self, dz):
        cofr = self.view.center_of_rotation
        cofr_method = self.view.center_of_rotation_method
        camera = self.view.camera
        cpos = self.camera_position.origin()
        vd = camera.view_direction()
        import numpy
        cc = cofr-cpos
        shift_vec = numpy.dot(cc/numpy.linalg.norm(cc), vd) *vd * dz
        from chimerax.core.geometry import translation
        t = translation(shift_vec)
        self.view.center_of_rotation += shift_vec
        self.view.center_of_rotation_method = cofr_method
        camera.set_position(t*camera.position)

    def _drawing_geometry(self):
        from chimerax.surface.shapes import box_geometry
        return box_geometry((-1,-1,-0.01), (1,1,0.01))

    def _set_drawing_position(self):
        from chimerax.core.geometry import Place, scale
        scale = 300*self.pixel_size()
        p = Place(axes=self.view.camera.position.axes()*scale, origin=self.view.center_of_rotation)
        self._drawing.position = p

    def _set_drawing_color(self):
        from chimerax.core.colors import contrast_with, Color
        import numpy
        color = numpy.array([0,0,0,0.5], numpy.float32)
        color[:3] = contrast_with(self.view.background_color)
        self._drawing.color = Color(color).uint8x4()

Attachments (1)

z_shift.mp4 (9.2 MB ) - added by Tristan Croll 6 years ago.

Change History (13)

by Tristan Croll, 6 years ago

Attachment: z_shift.mp4 added

comment:1 by Tristan Croll, 6 years ago

Oh yeah - in Clipper I've mapped it to ctrl-right.

comment:2 by Tristan Croll, 6 years ago

A current limitation as written is that it will only work correctly with the orthographic camera since it does move the camera alongside the cofr. This is because my ZoomMouseMode in Clipper sets the front and back clipping planes based on fractional distances between cofr and camera, so it needs to move to keep things consistent. Would need to rethink things somewhat for other camera modes.

comment:3 by Tom Goddard, 6 years ago

Please describe in words what your new feature is supposed to do. Providing lots of code makes it very time consuming to figure out.

Is this new mouse mode specifically for center of view rotation method? If so, why doesn't it just move the near/far clip planes back and forward since the center of view rotation is mid-way between those?

I agree we aren't going to add modes that should work in perspective projection and don't -- that will just look broken to the user.

The transparent square I guess is intended to tell the user they are invoking the mode. If the model is transparent it will likely make the model invisible, not so good. I don't understand the purpose of the indicator if near/far clip planes are moving, since the clipping will then visually convey the change.

I don't think ctrl-right mouse binding will work well on Mac since I believe Qt reports ctrl-left on Mac as ctrl-right because ctrl-left is the standard emulation for right click on Mac.

comment:4 by Tristan Croll, 6 years ago

The point is to provide a method to "pan" in the Z direction. Without it, in center of view rotation mode the only way to change the depth of the rotation point is to rotate by 90 degrees, shift sideways, then rotate back - cumbersome and fiddly for the user. Yes, probably only useful for center of rotation method.

Moving the cofr and camera backwards and forwards seems better to me than moving the clip planes, since it should work even if clip planes are disabled. Not sure where the callback is that's achieving it, but moving cofr and camera like this leads to the clipping planes automatically shifting.

I just tried it out in projection mode and it actually comes out quite well - gives a fairly pleasing sensation of "flying through" the model. Turns out the Clipper ZoomMouseMode is broken for that camera, though - will fix that ASAP.

The point of the transparent square is to make it easier to see exactly where the new centre of rotation will be - it sits in the plane of the cofr, so the depth of the new centre can be inferred from what's in front and what's behind. It's more precise than trying to visually interpolate between the clipping planes (particularly if they're fairly far apart).

I'd forgotten about the ctrl-right issue on the Mac. Will think about alternatives.

in reply to:  6 comment:5 by Tom Goddard, 6 years ago

Here is the documentation (from core/graphics/view.py) of where the center of  view rotation point lies.  The position is defined by the clip planes or scene bounding box.  So moving the center of view point independently doesn't make sense with this definition.  Of course the definition can be changed, but then you run into the problem of how to define the position when the camera is moved (arbitrary rotation and translation).  You would need a complete solution.

  def _center_of_view_cofr(self):
        '''
        Keep the center of rotation in the middle of the view at a depth
        midway between near and far clip planes.  If only the near plane
        or only the far plane is enabled use center on that plane.  If neither
        near nor far planes are enabled use depth equal to center of bounding box.
        '''

in reply to:  7 ; comment:6 by Tristan Croll, 6 years ago

Hmm... I think that's changed since the last time I looked? Will make 
the change, but now I'm curious why the approach I'm taking here works 
at all! I'm only touching camera and cofr positions, but if I watch in 
the side view the clipping planes move in perfect sync exactly as 
desired. But looking through your code I can't for the life of me see 
why that is.

On 2019-11-12 18:48, ChimeraX wrote:

in reply to:  8 ; comment:7 by Tom Goddard, 6 years ago

Ok, the documentation does seem out of data.  Looking at the code fo the _center_of_view_cofr() routine shows it doesn't using the scene bounding box when no clip planes are enabled, instead it preserves the distance from the camera to the rotation point.  So your method should work as long as near or far clip planes are not enabled, but should not work if those are enabled.

in reply to:  9 ; comment:8 by Tom Goddard, 6 years ago

Actually I mis-stated it.  It does not remember the prior distance from camera to rotation center.  The comment in the code says what it does.

            # No clip planes.
            # Keep the center of rotation in the middle of the view at a depth
            # such that the new and previous center of rotation are in the same
            # plane perpendicular to the camera view direction.

in reply to:  10 ; comment:9 by Tristan Croll, 6 years ago

The implementation of _center_of_view_cofr() has drifted somewhat from 
my original intent (the code beyond the `else:`), which is that in this 
mode, after the center of rotation is initially set then normal 
interactions should only ever move it perpendicular to the camera axis. 
I think the shortcut of placing it midway between near and far clip 
planes when they're defined is a sensible one, but I don't think the 
behaviour when only one clip plane is defined makes sense - you end up 
either with half your model invisible, or rotating around a point well 
outside the model. In those cases I think it'd be better to use the 
no-clip-planes code path.

Anyway, I just found out why my code as written is working for me: 
Clipper's ZoomMouseMode is defining the near and far clip planes as 
CameraClipPlane, so of course they move when the camera moves. Can 
rewrite if you'd prefer near and far clip to always be defined in scene 
coords.



On 2019-11-12 19:46, ChimeraX wrote:

in reply to:  11 ; comment:10 by Tom Goddard, 6 years ago

Not sure what you mean "should only ever move perpendicular to the camera axis".  If the camera rotates what camera axis are you talking about, the one before it rotated, after it rotated?  The code is using the axis after it rotated.  I agree the rotation point position with just near clip plane on the plane is bad.  How would you place the point in that case?  Currently the no clip planes case simply takes the current center of rotation (which is from a different center of rotation mode if you are just switching into center of view mode) and moves it perpendicular to the camera view axis to the center.  It could do that for the single plane case.

in reply to:  12 ; comment:11 by Tristan Croll, 6 years ago

Yes, I wasn’t really clear there. The idea is that once it’s placed at some point on the camera ray, moving the camera via normal user interaction should only cause the cofr to move in the plane perpendicular to the *new* view direction. At the user level, what it means is that the cofr remains fixed when rotating and zooming, and only moves when they translate. Adding this new mode provides the missing degree of freedom to give them full mouse-driven control over exactly where the centre of rotation is.

I do understand the question of where to put the cofr in general when it needs to be reset (when the view jumps or when “cofr center” is first called, though - and I guess it makes sense to move it when the clipping planes change to ensure it doesn’t end up lost outside the displayed region. The following might work:

- if the far clipping plane is missing, replace its plane point with the intersection of the camera ray and the back of the bounding box.

- if the near plane is missing, use the front of the bounding box.

- Set it midway between the above points, as per your existing code when both clip planes are present.
 
Those rules should keep the existing behaviour when both clip planes are active, and I think would do a reasonable job in most other cases.
 


in reply to:  13 ; comment:12 by Tom Goddard, 6 years ago

You describe how it works now.  The initial placement with a single clip plane could work using the bounding box as you describe. The current code does not distinguish initial placement from updating the placement.  I think I would instead just use the no-clip plane behavior of use the depth of the previous center.  If you enable the near clip plane and the depth of the rotation point is not tied to the clip plane then it won't move -- and may end up in front of the clip plane.  Discussion of how center of view works with a single clip plane should be in a separate ticket.

Sticking to the topic of this ticket your proposed translate z mode will have to move the clip planes if near/far planes are enabled and the rotation point is mid-way between those planes, and it will need to work in all camera modes where it makes sense, with and without clip planes.

Note: See TracTickets for help on using tickets.