Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

High-level fj-viewer interface #803

Merged
merged 9 commits into from
Jul 12, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 7 additions & 17 deletions crates/fj-viewer/src/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::f64::consts::FRAC_PI_2;
use fj_interop::mesh::Mesh;
use fj_math::{Aabb, Point, Scalar, Transform, Triangle, Vector};

use crate::screen::{Position, Size};
use crate::screen::NormalizedPosition;

/// The camera abstraction
///
Expand Down Expand Up @@ -104,33 +104,23 @@ impl Camera {
.inverse_transform_point(&Point::<3>::origin())
}

/// Transform the position of the cursor on the near plane to model space.
/// Transform a normalized cursor position on the near plane to model space.
pub fn cursor_to_model_space(
&self,
cursor: Position,
size: Size,
cursor: NormalizedPosition,
) -> Point<3> {
let [width, height] = size.as_f64();
let aspect_ratio = width / height;

// Cursor position in normalized coordinates (-1 to +1) with
// aspect ratio taken into account.
let x = cursor.x / width * 2. - 1.;
let y = -(cursor.y / height * 2. - 1.) / aspect_ratio;

// Cursor position in camera space.
let f = (self.field_of_view_in_x() / 2.).tan() * self.near_plane();
let cursor =
Point::origin() + Vector::from([x * f, y * f, -self.near_plane()]);
let cursor = Point::origin()
+ Vector::from([cursor.x * f, cursor.y * f, -self.near_plane()]);

self.camera_to_model().inverse_transform_point(&cursor)
}

/// Compute the point on the model, that the cursor currently points to.
pub fn focus_point(
&self,
size: Size,
cursor: Option<Position>,
cursor: Option<NormalizedPosition>,
mesh: &Mesh<fj_math::Point<3>>,
) -> FocusPoint {
let cursor = match cursor {
Expand All @@ -140,7 +130,7 @@ impl Camera {

// Transform camera and cursor positions to model space.
let origin = self.position();
let cursor = self.cursor_to_model_space(cursor, size);
let cursor = self.cursor_to_model_space(cursor);
let dir = (cursor - origin).normalize();

let mut min_t = None;
Expand Down
37 changes: 30 additions & 7 deletions crates/fj-viewer/src/input/event.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
use crate::screen::Position;
use crate::{camera::FocusPoint, screen::NormalizedPosition};

/// An input event
pub enum Event {
/// The cursor has moved to another position
CursorMoved(Position),
/// A new focus point was selected.
FocusPoint(FocusPoint),

/// A key has been pressed or released
Key(Key, KeyState),
/// Move the view up, down, left or right
Pan {
/// The normalized position of the cursor before input
previous: NormalizedPosition,
/// The normalized position of the cursor after input
current: NormalizedPosition,
},

/// The user scrolled
Scroll(MouseScrollDelta),
/// Rotate the view around the focus point
Orbit {
/// The normalized position of the cursor before input
previous: NormalizedPosition,
/// The normalized position of the cursor after input
current: NormalizedPosition,
},
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The metaphor that Fornjot's UI follows, is that you manipulate the model, not an abstract camera. So maybe Translate and Rotate would be better names than Pan/Orbit.

I realize that this metaphor isn't fully followed everywhere, especially in code comments. So there's no need to follow it here right now, and I certainly won't block merging this pull request on this detail. I'm just mentioning it, in case you have thoughts, and to document where I'd like to see things go mid- to long-term.

(Side note: Maybe we should rename Camera to Observer or Spectator to reinforce the metaphor and reduce confusion. Maybe not a good idea.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I'll fix this up.

On a related note. I feel like the camera object shouldn't exist in fj-window at all. Seems like it belongs in fj-viewer.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I guess I mean the lifetime could also be controlled by the viewer. Right now it is instantiated in fj-window.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that makes sense. The way all of this is structured pretty much reflects the fact, that it lived in a single crate not long ago. It probably makes sense to have a single Viewer struct with methods like handle_input and draw_graphics. No need, really, for fj-window to deal with input::Handler, Renderer, etc. separately.


/// Move the view forwards and backwards
Zoom(f64),

/// Application should exit
Exit,

/// Toggle the shaded display of the model.
ToggleModel,
/// Toggle the model's wireframe.
ToggleMesh,
/// Toggle debug information.
ToggleDebug,
}

/// Describes a difference in the vertical mouse scroll wheel state.
Expand Down
107 changes: 26 additions & 81 deletions crates/fj-viewer/src/input/handler.rs
Original file line number Diff line number Diff line change
@@ -1,113 +1,58 @@
use fj_interop::mesh::Mesh;
use fj_math::Point;

use super::{
event::KeyState, movement::Movement, rotation::Rotation, zoom::Zoom, Event,
Key,
};
use crate::{
camera::Camera,
screen::{Position, Size},
};
use super::{movement::Movement, rotation::Rotation, zoom::Zoom, Event};
use crate::camera::{Camera, FocusPoint};

/// Input handling abstraction
///
/// Takes user input and applies them to application state.
pub struct Handler {
cursor: Option<Position>,
focus_point: FocusPoint,

movement: Movement,
rotation: Rotation,
zoom: Zoom,
}

impl Handler {
/// Returns the state of the cursor position.
pub fn cursor(&self) -> Option<Position> {
self.cursor
}

/// Handle an input event
pub fn handle_event(
&mut self,
event: Event,
screen_size: Size,
mesh: &Mesh<Point<3>>,
camera: &mut Camera,
actions: &mut Actions,
) {
match event {
Event::CursorMoved(position) => {
if let Some(previous) = self.cursor {
let diff_x = position.x - previous.x;
let diff_y = position.y - previous.y;

self.movement.apply(self.cursor, camera, screen_size);
self.rotation.apply(diff_x, diff_y, camera);
}

self.cursor = Some(position);
}
Event::Key(Key::Escape, KeyState::Pressed) => actions.exit = true,

Event::Key(Key::Key1, KeyState::Pressed) => {
actions.toggle_model = true
}
Event::Key(Key::Key2, KeyState::Pressed) => {
actions.toggle_mesh = true
}
Event::Key(Key::Key3, KeyState::Pressed) => {
actions.toggle_debug = true
}

Event::Key(Key::MouseLeft, KeyState::Pressed) => {
let focus_point =
camera.focus_point(screen_size, self.cursor(), mesh);

self.rotation.start(focus_point);
Event::FocusPoint(focus_point) => self.focus_point = focus_point,
Event::Pan { previous, current } => self.movement.apply(
previous,
current,
&self.focus_point,
camera,
),
Event::Orbit { previous, current } => self.rotation.apply(
previous,
current,
&self.focus_point,
camera,
),
Event::Zoom(zoom_delta) => {
self.zoom.apply(zoom_delta, &self.focus_point, camera)
}
Event::Key(Key::MouseLeft, KeyState::Released) => {
self.rotation.stop();
}
Event::Key(Key::MouseRight, KeyState::Pressed) => {
let focus_point =
camera.focus_point(screen_size, self.cursor(), mesh);

self.movement.start(focus_point, self.cursor);
}
Event::Key(Key::MouseRight, KeyState::Released) => {
self.movement.stop();
}

Event::Scroll(delta) => {
self.zoom.push(delta);
}

_ => {}
Event::Exit => actions.exit = true,
Event::ToggleModel => actions.toggle_model = true,
Event::ToggleMesh => actions.toggle_mesh = true,
Event::ToggleDebug => actions.toggle_debug = true,
}
}

/// Update application state from user input.
pub fn update(
&mut self,
delta_t: f64,
camera: &mut Camera,
screen_size: Size,
mesh: &Mesh<Point<3>>,
) {
let focus_point = camera.focus_point(screen_size, self.cursor(), mesh);
self.zoom.apply_to_camera(delta_t, focus_point, camera);
}
}

impl Default for Handler {
fn default() -> Self {
Self {
cursor: None,
focus_point: FocusPoint::none(),

movement: Movement::new(),
rotation: Rotation::new(),
zoom: Zoom::new(),
movement: Movement,
rotation: Rotation,
zoom: Zoom,
}
}
}
Expand Down
64 changes: 21 additions & 43 deletions crates/fj-viewer/src/input/movement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,35 @@ use fj_math::{Point, Scalar, Transform, Vector};

use crate::{
camera::{Camera, FocusPoint},
screen::{Position, Size},
screen::NormalizedPosition,
};

pub struct Movement {
focus_point: FocusPoint,
cursor: Option<Position>,
}
pub struct Movement;

impl Movement {
pub fn new() -> Self {
Self {
focus_point: FocusPoint::none(),
cursor: None,
}
}

pub fn start(&mut self, focus_point: FocusPoint, cursor: Option<Position>) {
self.focus_point = focus_point;
self.cursor = cursor;
}

pub fn stop(&mut self) {
self.focus_point = FocusPoint::none();
}

pub fn apply(
&mut self,
cursor: Option<Position>,
previous: NormalizedPosition,
current: NormalizedPosition,
focus_point: &FocusPoint,
camera: &mut Camera,
size: Size,
) {
if let (Some(previous), Some(cursor)) = (self.cursor, cursor) {
let previous = camera.cursor_to_model_space(previous, size);
let cursor = camera.cursor_to_model_space(cursor, size);

if let Some(focus_point) = self.focus_point.0 {
let d1 = Point::distance(&camera.position(), &cursor);
let d2 = Point::distance(&camera.position(), &focus_point);

let diff = (cursor - previous) * d2 / d1;
let offset = camera.camera_to_model().transform_vector(&diff);

camera.translation = camera.translation
* Transform::translation(Vector::from([
offset.x,
offset.y,
Scalar::ZERO,
]));
}
let previous = camera.cursor_to_model_space(previous);
let cursor = camera.cursor_to_model_space(current);

if let Some(focus_point) = focus_point.0 {
let d1 = Point::distance(&camera.position(), &cursor);
let d2 = Point::distance(&camera.position(), &focus_point);

let diff = (cursor - previous) * d2 / d1;
let offset = camera.camera_to_model().transform_vector(&diff);

camera.translation = camera.translation
* Transform::translation(Vector::from([
offset.x,
offset.y,
Scalar::ZERO,
]));
}

self.cursor = cursor;
}
}
Loading