Moving the camera with the middle mouse button

(Abovegame) #1

I’m currently stuck with this. I’ve got some idea how to do it, but it seems I’m doing something wrong. If anyone could look at the code and see if I’m overlooking something silly. Basically I am trying to drag the camera around with the middle mouse button. Please note that the camera has the Perspective projection type.

pub struct EditorCameraMovementSystem {
   dragging: bool,
   drag_start: Option<Point3<f32>>,
}

fn run(&mut self, (mut transforms, camera, input, dims): Self::SystemData) {
       if let Some((mut t, cam)) = (&mut transforms,&camera).join().next() {
           if let Some((x,y)) = input.mouse_position() {
               // All the logic needs be here. 
               // We get the middle mouse button input ( true / false).
               let is_mouse_middle = input.mouse_button_is_down(MouseButton::Middle);
               // If the dragging has not started yet but the middle
               // mouse button is pressed, that means that the dragging 
               // has started, and we acquire the starting point.
               if !self.dragging && is_mouse_middle { 
                   // Now we need to tell the system that the dragging has started
                   // so it has it in mind, we do not want to keep writing the 
                   // starting point everytime the mouse moves. Hence self.dragging = true.
                   self.dragging = true;
                   let ray = cam.projection().screen_ray(
                       Point2::new(x, y),
                       Vector2::new(dims.width(), dims.height()),
                       t,
                   );
                   // Here we are storing the starting point into our struct field. 
                   if let Some(distance) = ray.intersect_plane(&Plane::with_z(0f32)) {
                       self.drag_start = Some(ray.at_distance(distance));
                   }
               } else if let Some(p) = self.drag_start {
                   if is_mouse_middle {
                       // Here we will get the current point of the mouse
                       // but in world space. The same way as we got the
                       // drag starting position.
                       let ray = cam.projection().screen_ray(
                           Point2::new(x, y),
                           Vector2::new(dims.width(), dims.height()),
                           t,
                       );
                       if let Some(distance) = ray.intersect_plane(&Plane::with_z(0f32)) {
                           let d = ray.at_distance(distance);
                           // Inversing the dragging.
                           let x = -(d.x - p.x);
                           let y = -(d.y - p.y);
                           t.set_translation(Vector3::new(x, y, t.translation().z));
                       }

                   } else {
                       // Reset the values
                       self.dragging = false;
                       self.drag_start = None;
                   }
               }
           }
       }  
   }
(Azriel Hoh) #2

The intent looks correct, what are you seeing? (hard to tell what’s wrong without knowing the symptom)

(Duncan Fairbanks) #3

I think you can do this with less branching. I am doing something similar that just requires storing the previous mouse position (not the previous world space coordinates). I recall that on my first attempt at implementing this, I stored the previous world space coordinates instead and it was subtly broken. I can’t remember exactly why, but I had to draw out the geometry to figure it out.

My code boils down to this:

if input.mouse_button_is_down(MouseButton::Left) {
    target_translation = floor_drag_translation(
        floor_plane,
        camera_tfm,
        camera_proj,
        screen_dims,
        cursor_pos,
        self.prev_cursor_pos,
    );
}

self.prev_cursor_pos = cursor_pos;

Then I implement floor_drag_translation like so:

fn _floor_drag_translation(
    floor_plane: &Plane,
    prev_screen_ray: &Line,
    screen_ray: &Line,
) -> Vector3<f32> {
    let p_now = line_plane_intersection(screen_ray, floor_plane);
    if let LinePlaneIntersection::IntersectionPoint(p_now) = p_now {
        let p_prev = line_plane_intersection(prev_screen_ray, floor_plane);
        if let LinePlaneIntersection::IntersectionPoint(p_prev) = p_prev {
            return p_prev - p_now;
        }
    }

    Vector3::zeros()
}

pub fn floor_drag_translation(
    floor_plane: &Plane,
    camera_tfm: &Transform,
    camera_proj: &Projection,
    dims: &ScreenDimensions,
    cursor_pos: Point2<f32>,
    prev_cursor_pos: Point2<f32>,
) -> Vector3<f32> {
    let prev_screen_ray = screen_ray(camera_tfm, camera_proj, dims, prev_cursor_pos);
    let screen_ray = screen_ray(camera_tfm, camera_proj, dims, cursor_pos);

    _floor_drag_translation(floor_plane, &prev_screen_ray, &screen_ray)
}
(Abovegame) #4

Here’s a video of the system. I also get some rendering issues which don’t show in the recordings, basically when i start dragging another grid shows up for some reason.

1 Like
(Marco Alka) #5

Did you know that you can use the mouse like an input axis? By doing that, you only ever get the delta by which you have to move the translation, which seems to be a lot simpler code to me, plus you can rebind moving easily or make it a setting for the player. Code untested…

/*!
    @import /amethyst_input/src/bindings.rs#Bindings, StringBindings
    Bindings<StringBindings>
*/

(
    axes: {
        "move_field_x": Mouse(
            axis: X,
            over_extendable: true,
            radius: 1.0
        ),
        "move_field_y": Mouse(
            axis: Y,
            over_extendable: true,
            radius: 1.0
        ),
    },
    actions: {
      "move_field": [MouseButton(Middle)],
    },
)
if input.action_is_down("move_field") {
    let d_x = input.axis_value("move_field_x");
    let d_y = input.axis_value("move_field_y");

    // do scaling for faster movement etc. here

    t.move_right(d_x);
    t.move_up(d_y);
}
2 Likes
(Abovegame) #6

I don’t really see anything that would represent the mouse as an input axis.
This is all that shows up in the API for the Axis, at least on stable.

pub enum Axis {
    Emulated {
        pos: Button,
        neg: Button,
    },
    Controller {
        controller_id: u32,
        axis: ControllerAxis,
        invert: bool,
        dead_zone: f64,
    },
    MouseWheel {
        horizontal: bool,
    },
}

EDIT:
It seems that it is on master ( https://github.com/amethyst/amethyst/blob/master/amethyst_input/src/axis.rs#L30 ). I’ll try it when 0.14 comes out! :slight_smile:

EDIT #2:
I’ve managed to do it :star_struck: !

(
    axes: {
        "move_field_x": Mouse(
            axis: X,
            over_extendable: true,
            radius: 1.0
        ),
        "move_field_y": Mouse(
            axis: Y,
            over_extendable: true,
            radius: 1.0
        ),
    },
    actions: {
        "move_field": [[Mouse(Middle)]],
    },
)

Works like a charm thanks to @maruru! And i don’t get any clunky behaviour as well.

1 Like