Repository: vispy/vispy

Issue:
Add support for "TurntableCamera" translate speed.
I have some issues when using a `vispy.scene.cameras.TurntableCamera` with user defined distance.

In my application the camera is sometime set quite close of the origin and I suspect the combination of that distance and the large scene bounding box is enough to generate values in `vispy.scene.cameras.TurntableCamera._dist_to_trans` that predate correct traveling as you can see in this video: https://drive.google.com/file/d/0B_IQZQdc4Vy8S0dTNEd5Q1hLWE0/view?usp=sharing

For now I have created a new camera that inherits from `TurntableCamera` and overrides `_dist_to_trans` as follows:

``` python
    def _dist_to_trans(self, distance):
        translate = np.asarray(TurntableCamera._dist_to_trans(self, distance))
        translate *= self.__translate_speed

        return translate
```



Discussion:
I had a pull request (#714) that implemented a consistent panning speed for cameras, but the camera code changed so much before it was merged that it didn't work anymore. It worked like this: if you imagine a flat surface in front of the camera, as if there is a glass window in front of you, then panning the view is like grabbing a part of that window and moving yourself. That is, the point under your mouse when you start panning stays under your mouse. The advantage of this is the camera speed scales with distance. The disadvantage is if you move your origin (like when you are using the flying camera), it will feel like the camera is panning too fast or too slow versus what a user would intuitively think.

I kind of don't like the current panning system because it feels a little "slippery". What you have shown here is an extreme case.

In an ideal world, I think it would be awesome to implement camera panning by picking a visual to "grab on to" while the camera moves, as if you are moving the scene by using that object as a handle. This would make panning very intuitive and scale-independent. This might be possible once picking is implemented, but it might also be a huge pain to implement.

In order to reimplement the PR, I would have to sit down and really understand the camera changes (the camera code is more complex so it could be more general). However, I do explain the concept inside the PR.

That looked great and seems to be perfect in my context.

I don't see a problem with having some multiplicative parameter like you have @KelSolaar. @campagnola what did you and @almarklein have in mind for this sort of thing?

@jdreaver yeah we want picking, hopefully one day the "handle" thing will be possible. But for now we might have to go for a less ideal but more easily workable solution. It would be good if you could try to reimplement your changes in the new system at some point, but that will probably take some work.

> @jdreaver yeah we want picking, hopefully one day the "handle" thing will be possible. But for now we might have to go for a less ideal but more easily workable solution. It would be good if you could try to reimplement your changes in the new system at some point, but that will probably take some work.

Agreed. Simple is definitely best for now.

I agree that the mouse should stay in the same spot. What's shown in the video is just wrong :)

Should I create a PR with an attribute like in my first message?

Probably best to wait until #928 is in before starting on this.

@jdreaver we are going to try to merge #928 very soon, will you have time to try to translate your camera changes using the new system? It should be fairly stable once that is merged.

If not, I can implement the simpler solution of just adding a native multiplicative factor for the `dist_to_trans` to solve @KelSolaar's case.

Cool! I am on vacation for the next few weeks, so I won't have time until
I'm back.
On Jul 5, 2015 2:27 PM, "Eric Larson" notifications@github.com wrote:

> @jdreaver https://github.com/jdreaver we are going to try to merge #928
> https://github.com/vispy/vispy/pull/928 very soon, will you have time
> to try to translate your camera changes using the new system? It should be
> fairly stable once that is merged.
> 
> If not, I can implement the simpler solution of just adding a native
> multiplicative factor for the dist_to_trans to solve @KelSolaar
> https://github.com/KelSolaar's case.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/vispy/vispy/issues/914#issuecomment-118617764.

Okay, in the meantime I'll add a simple parameter to allow manual adjustment then. If you could come back to your PR whenever you get time, it would be very helpful.

@jdreaver @Eric89GXL - any updates on this? This would be very useful to me too!

I don't think anyone's worked on this :/ I never got around to figuring out the new camera code (well, I guess it isn't so new anymore haha).


Resolving patch:
diff --git a/vispy/scene/cameras/arcball.py b/vispy/scene/cameras/arcball.py
--- a/vispy/scene/cameras/arcball.py
+++ b/vispy/scene/cameras/arcball.py
@@ -27,6 +27,8 @@ class ArcballCamera(Base3DRotationCamera):
         The distance of the camera from the rotation point (only makes sense
         if fov > 0). If None (default) the distance is determined from the
         scale_factor and fov.
+    translate_speed : float
+        Scale factor on translation speed when moving the camera center point.
     **kwargs : dict
         Keyword arguments to pass to `BaseCamera`.
 
@@ -43,12 +45,13 @@ class ArcballCamera(Base3DRotationCamera):
 
     _state_props = Base3DRotationCamera._state_props + ('_quaternion',)
 
-    def __init__(self, fov=0.0, distance=None, **kwargs):
+    def __init__(self, fov=0.0, distance=None, translate_speed=1.0, **kwargs):
         super(ArcballCamera, self).__init__(fov=fov, **kwargs)
 
         # Set camera attributes
         self._quaternion = Quaternion()
         self.distance = distance  # None means auto-distance
+        self.translate_speed = translate_speed
 
     def _update_rotation(self, event):
         """Update rotation parmeters based on mouse movement"""
@@ -73,7 +76,8 @@ def _dist_to_trans(self, dist):
         rot, x, y, z = self._quaternion.get_axis_angle()
         tr = MatrixTransform()
         tr.rotate(180 * rot / np.pi, (x, y, z))
-        dx, dz, dy = np.dot(tr.matrix[:3, :3], (dist[0], dist[1], 0.))
+        dx, dz, dy = np.dot(tr.matrix[:3, :3],
+                            (dist[0], dist[1], 0.)) * self.translate_speed
         return dx, dy, dz
 
     def _get_dim_vectors(self):
diff --git a/vispy/scene/cameras/turntable.py b/vispy/scene/cameras/turntable.py
--- a/vispy/scene/cameras/turntable.py
+++ b/vispy/scene/cameras/turntable.py
@@ -34,6 +34,8 @@ class TurntableCamera(Base3DRotationCamera):
         The distance of the camera from the rotation point (only makes sense
         if fov > 0). If None (default) the distance is determined from the
         scale_factor and fov.
+    translate_speed : float
+        Scale factor on translation speed when moving the camera center point.
     **kwargs : dict
         Keyword arguments to pass to `BaseCamera`.
 
@@ -52,7 +54,7 @@ class TurntableCamera(Base3DRotationCamera):
                                                         'azimuth', 'roll')
 
     def __init__(self, fov=0.0, elevation=30.0, azimuth=30.0, roll=0.0,
-                 distance=None, **kwargs):
+                 distance=None, translate_speed=1.0, **kwargs):
         super(TurntableCamera, self).__init__(fov=fov, **kwargs)
 
         # Set camera attributes
@@ -60,6 +62,7 @@ def __init__(self, fov=0.0, elevation=30.0, azimuth=30.0, roll=0.0,
         self.elevation = elevation
         self.roll = roll  # interaction not implemented yet
         self.distance = distance  # None means auto-distance
+        self.translate_speed = translate_speed
 
     @property
     def elevation(self):
@@ -142,9 +145,10 @@ def _dist_to_trans(self, dist):
         rae = np.array([self.roll, self.azimuth, self.elevation]) * np.pi / 180
         sro, saz, sel = np.sin(rae)
         cro, caz, cel = np.cos(rae)
-        dx = (+ dist[0] * (cro * caz + sro * sel * saz)
-              + dist[1] * (sro * caz - cro * sel * saz))
-        dy = (+ dist[0] * (cro * saz - sro * sel * caz)
-              + dist[1] * (sro * saz + cro * sel * caz))
-        dz = (- dist[0] * sro * cel + dist[1] * cro * cel)
+        d0, d1 = dist[0], dist[1]
+        dx = (+ d0 * (cro * caz + sro * sel * saz)
+              + d1 * (sro * caz - cro * sel * saz)) * self.translate_speed
+        dy = (+ d0 * (cro * saz - sro * sel * caz)
+              + d1 * (sro * saz + cro * sel * caz)) * self.translate_speed
+        dz = (- d0 * sro * cel + d1 * cro * cel) * self.translate_speed
         return dx, dy, dz


Test patch: