diff --git a/demo/common/src/camera.rs b/demo/common/src/camera.rs new file mode 100644 index 00000000..cbd6559e --- /dev/null +++ b/demo/common/src/camera.rs @@ -0,0 +1,195 @@ +// pathfinder/demo/common/src/camera.rs +// +// Copyright © 2019 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Camera management code for the demo. + +// TODO(#140, pcwalton): Move some of this out of the demo and into the library +// proper. + +use crate::window::{OcularTransform, View}; +use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32, Point3DF32}; +use pathfinder_geometry::basic::rect::RectF32; +use pathfinder_geometry::basic::transform2d::Transform2DF32; +use pathfinder_geometry::basic::transform3d::{Perspective, Transform3DF32}; +use std::f32::consts::FRAC_PI_4; + +const NEAR_CLIP_PLANE: f32 = 0.01; +const FAR_CLIP_PLANE: f32 = 10.0; + +// Half of the eye separation distance. +const DEFAULT_EYE_OFFSET: f32 = 0.025; + +pub enum Camera { + TwoD(Transform2DF32), + ThreeD { + // The ocular transform used for rendering of the scene to the scene framebuffer. If we are + // performing stereoscopic rendering, this is then reprojected according to the eye + // transforms below. + scene_transform: OcularTransform, + // For each eye, the perspective from camera coordinates to display coordinates, + // and the view transform from world coordinates to camera coordinates. + eye_transforms: Vec, + // The modelview transform from world coordinates to SVG coordinates + modelview_transform: CameraTransform3D, + // The camera's velocity (in world coordinates) + velocity: Point3DF32, + }, +} + +impl Camera { + pub fn new(mode: Mode, view_box: RectF32, viewport_size: Point2DI32) -> Camera { + if mode == Mode::TwoD { + Camera::new_2d(view_box, viewport_size) + } else { + Camera::new_3d(mode, view_box, viewport_size) + } + } + + fn new_2d(view_box: RectF32, viewport_size: Point2DI32) -> Camera { + let scale = i32::min(viewport_size.x(), viewport_size.y()) as f32 + * scale_factor_for_view_box(view_box); + let origin = viewport_size.to_f32().scale(0.5) - view_box.size().scale(scale * 0.5); + Camera::TwoD(Transform2DF32::from_scale(&Point2DF32::splat(scale)).post_translate(origin)) + } + + fn new_3d(mode: Mode, view_box: RectF32, viewport_size: Point2DI32) -> Camera { + let viewport_count = mode.viewport_count(); + + let fov_y = FRAC_PI_4; + let aspect = viewport_size.x() as f32 / viewport_size.y() as f32; + let projection = + Transform3DF32::from_perspective(fov_y, aspect, NEAR_CLIP_PLANE, FAR_CLIP_PLANE); + let perspective = Perspective::new(&projection, viewport_size); + + // Create a scene transform by moving the camera back from the center of the eyes so that + // its field of view encompasses the field of view of both eyes. + let z_offset = -DEFAULT_EYE_OFFSET * projection.c0.x(); + let scene_transform = OcularTransform { + perspective, + modelview_to_eye: Transform3DF32::from_translation(0.0, 0.0, z_offset), + }; + + // For now, initialize the eye transforms as copies of the scene transform. + let eye_offset = DEFAULT_EYE_OFFSET; + let eye_transforms = (0..viewport_count) + .map(|viewport_index| { + let this_eye_offset = if viewport_index == 0 { + eye_offset + } else { + -eye_offset + }; + OcularTransform { + perspective, + modelview_to_eye: Transform3DF32::from_translation(this_eye_offset, 0.0, 0.0), + } + }) + .collect(); + + Camera::ThreeD { + scene_transform, + eye_transforms, + modelview_transform: CameraTransform3D::new(view_box), + velocity: Point3DF32::default(), + } + } + + pub fn is_3d(&self) -> bool { + match *self { + Camera::ThreeD { .. } => true, + Camera::TwoD { .. } => false, + } + } + + pub fn mode(&self) -> Mode { + match *self { + Camera::ThreeD { + ref eye_transforms, .. + } if eye_transforms.len() >= 2 => Mode::VR, + Camera::ThreeD { .. } => Mode::ThreeD, + Camera::TwoD { .. } => Mode::TwoD, + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct CameraTransform3D { + position: Point3DF32, + pub yaw: f32, + pub pitch: f32, + scale: f32, +} + +impl CameraTransform3D { + fn new(view_box: RectF32) -> CameraTransform3D { + let scale = scale_factor_for_view_box(view_box); + CameraTransform3D { + position: Point3DF32::new( + 0.5 * view_box.max_x(), + -0.5 * view_box.max_y(), + 1.5 / scale, + 1.0, + ), + yaw: 0.0, + pitch: 0.0, + scale, + } + } + + pub fn offset(&mut self, vector: Point3DF32) -> bool { + let update = !vector.is_zero(); + if update { + let rotation = Transform3DF32::from_rotation(-self.yaw, -self.pitch, 0.0); + self.position = self.position + rotation.transform_point(vector); + } + update + } + + pub fn to_transform(&self) -> Transform3DF32 { + let mut transform = Transform3DF32::from_rotation(self.yaw, self.pitch, 0.0); + transform = transform.post_mul(&Transform3DF32::from_uniform_scale(2.0 * self.scale)); + transform = transform.post_mul(&Transform3DF32::from_translation( + -self.position.x(), + -self.position.y(), + -self.position.z(), + )); + + // Flip Y. + transform = transform.post_mul(&Transform3DF32::from_scale(1.0, -1.0, 1.0)); + + transform + } +} + +#[derive(Clone, Copy, PartialEq)] +pub enum Mode { + TwoD = 0, + ThreeD = 1, + VR = 2, +} + +impl Mode { + pub fn viewport_count(self) -> usize { + match self { + Mode::TwoD | Mode::ThreeD => 1, + Mode::VR => 2, + } + } + + pub fn view(self, viewport: u32) -> View { + match self { + Mode::TwoD | Mode::ThreeD => View::Mono, + Mode::VR => View::Stereo(viewport), + } + } +} + +pub fn scale_factor_for_view_box(view_box: RectF32) -> f32 { + 1.0 / f32::min(view_box.size().x(), view_box.size().y()) +} diff --git a/demo/common/src/lib.rs b/demo/common/src/lib.rs index 543841e8..ae9a23fc 100644 --- a/demo/common/src/lib.rs +++ b/demo/common/src/lib.rs @@ -13,16 +13,16 @@ #[macro_use] extern crate log; +use crate::camera::{Camera, Mode}; use crate::concurrent::DemoExecutor; use crate::device::{GroundProgram, GroundVertexArray}; use crate::ui::{DemoUI, UIAction}; -use crate::window::{Event, Keycode, OcularTransform, SVGPath, View, Window, WindowSize}; +use crate::window::{Event, Keycode, SVGPath, View, Window, WindowSize}; use clap::{App, Arg}; use image::ColorType; -use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32, Point3DF32}; +use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32}; use pathfinder_geometry::basic::rect::RectF32; -use pathfinder_geometry::basic::transform2d::Transform2DF32; -use pathfinder_geometry::basic::transform3d::{Perspective, Transform3DF32}; +use pathfinder_geometry::basic::transform3d::Transform3DF32; use pathfinder_geometry::color::{ColorF, ColorU}; use pathfinder_gl::GLDevice; use pathfinder_gpu::resources::ResourceLoader; @@ -36,7 +36,6 @@ use pathfinder_renderer::post::{DEFRINGING_KERNEL_CORE_GRAPHICS, STEM_DARKENING_ use pathfinder_renderer::scene::Scene; use pathfinder_svg::BuiltSVG; use pathfinder_ui::{MousePosition, UIEvent}; -use std::f32::consts::FRAC_PI_4; use std::fs::File; use std::io::Read; use std::path::PathBuf; @@ -54,9 +53,6 @@ const CAMERA_SCALE_SPEED_2D: f32 = 6.0; // How much the scene is scaled when a zoom button is clicked. const CAMERA_ZOOM_AMOUNT_2D: f32 = 0.1; -const NEAR_CLIP_PLANE: f32 = 0.01; -const FAR_CLIP_PLANE: f32 = 10.0; - const LIGHT_BG_COLOR: ColorU = ColorU { r: 248, g: 248, @@ -93,13 +89,11 @@ const APPROX_FONT_SIZE: f32 = 16.0; const MESSAGE_TIMEOUT_SECS: u64 = 5; -// Half of the eye separation distance. -const DEFAULT_EYE_OFFSET: f32 = 0.025; - pub const GRIDLINE_COUNT: i32 = 10; pub mod window; +mod camera; mod concurrent; mod device; mod ui; @@ -401,7 +395,8 @@ impl DemoApp where W: Window { ref mut velocity, .. } = self.camera { - let scale_factor = scale_factor_for_view_box(self.scene_metadata.view_box); + let scale_factor = + camera::scale_factor_for_view_box(self.scene_metadata.view_box); velocity.set_z(-CAMERA_VELOCITY / scale_factor); self.dirty = true; } @@ -411,7 +406,8 @@ impl DemoApp where W: Window { ref mut velocity, .. } = self.camera { - let scale_factor = scale_factor_for_view_box(self.scene_metadata.view_box); + let scale_factor = + camera::scale_factor_for_view_box(self.scene_metadata.view_box); velocity.set_z(CAMERA_VELOCITY / scale_factor); self.dirty = true; } @@ -421,7 +417,8 @@ impl DemoApp where W: Window { ref mut velocity, .. } = self.camera { - let scale_factor = scale_factor_for_view_box(self.scene_metadata.view_box); + let scale_factor = + camera::scale_factor_for_view_box(self.scene_metadata.view_box); velocity.set_x(-CAMERA_VELOCITY / scale_factor); self.dirty = true; } @@ -431,7 +428,8 @@ impl DemoApp where W: Window { ref mut velocity, .. } = self.camera { - let scale_factor = scale_factor_for_view_box(self.scene_metadata.view_box); + let scale_factor = + camera::scale_factor_for_view_box(self.scene_metadata.view_box); velocity.set_x(CAMERA_VELOCITY / scale_factor); self.dirty = true; } @@ -976,29 +974,6 @@ impl Options { } } -#[derive(Clone, Copy, PartialEq)] -pub enum Mode { - TwoD = 0, - ThreeD = 1, - VR = 2, -} - -impl Mode { - pub fn viewport_count(self) -> usize { - match self { - Mode::TwoD | Mode::ThreeD => 1, - Mode::VR => 2, - } - } - - pub fn view(self, viewport: u32) -> View { - match self { - Mode::TwoD | Mode::ThreeD => View::Mono, - Mode::VR => View::Stereo(viewport), - } - } -} - #[derive(Clone, Copy, PartialEq)] pub enum UIVisibility { None, @@ -1024,151 +999,6 @@ fn center_of_window(window_size: &WindowSize) -> Point2DF32 { window_size.device_size().to_f32().scale(0.5) } -enum Camera { - TwoD(Transform2DF32), - ThreeD { - // The ocular transform used for rendering of the scene to the scene framebuffer. If we are - // performing stereoscopic rendering, this is then reprojected according to the eye - // transforms below. - scene_transform: OcularTransform, - // For each eye, the perspective from camera coordinates to display coordinates, - // and the view transform from world coordinates to camera coordinates. - eye_transforms: Vec, - // The modelview transform from world coordinates to SVG coordinates - modelview_transform: CameraTransform3D, - // The camera's velocity (in world coordinates) - velocity: Point3DF32, - }, -} - -impl Camera { - fn new(mode: Mode, view_box: RectF32, viewport_size: Point2DI32) -> Camera { - if mode == Mode::TwoD { - Camera::new_2d(view_box, viewport_size) - } else { - Camera::new_3d(mode, view_box, viewport_size) - } - } - - fn new_2d(view_box: RectF32, viewport_size: Point2DI32) -> Camera { - let scale = i32::min(viewport_size.x(), viewport_size.y()) as f32 - * scale_factor_for_view_box(view_box); - let origin = viewport_size.to_f32().scale(0.5) - view_box.size().scale(scale * 0.5); - Camera::TwoD(Transform2DF32::from_scale(&Point2DF32::splat(scale)).post_translate(origin)) - } - - fn new_3d(mode: Mode, view_box: RectF32, viewport_size: Point2DI32) -> Camera { - let viewport_count = mode.viewport_count(); - - let fov_y = FRAC_PI_4; - let aspect = viewport_size.x() as f32 / viewport_size.y() as f32; - let projection = - Transform3DF32::from_perspective(fov_y, aspect, NEAR_CLIP_PLANE, FAR_CLIP_PLANE); - let perspective = Perspective::new(&projection, viewport_size); - - // Create a scene transform by moving the camera back from the center of the eyes so that - // its field of view encompasses the field of view of both eyes. - let z_offset = -DEFAULT_EYE_OFFSET * projection.c0.x(); - let scene_transform = OcularTransform { - perspective, - modelview_to_eye: Transform3DF32::from_translation(0.0, 0.0, z_offset), - }; - - // For now, initialize the eye transforms as copies of the scene transform. - let eye_offset = DEFAULT_EYE_OFFSET; - let eye_transforms = (0..viewport_count) - .map(|viewport_index| { - let this_eye_offset = if viewport_index == 0 { - eye_offset - } else { - -eye_offset - }; - OcularTransform { - perspective, - modelview_to_eye: Transform3DF32::from_translation(this_eye_offset, 0.0, 0.0), - } - }) - .collect(); - - Camera::ThreeD { - scene_transform, - eye_transforms, - modelview_transform: CameraTransform3D::new(view_box), - velocity: Point3DF32::default(), - } - } - - fn is_3d(&self) -> bool { - match *self { - Camera::ThreeD { .. } => true, - Camera::TwoD { .. } => false, - } - } - - fn mode(&self) -> Mode { - match *self { - Camera::ThreeD { - ref eye_transforms, .. - } if eye_transforms.len() >= 2 => Mode::VR, - Camera::ThreeD { .. } => Mode::ThreeD, - Camera::TwoD { .. } => Mode::TwoD, - } - } -} - -#[derive(Clone, Copy, Debug)] -struct CameraTransform3D { - position: Point3DF32, - yaw: f32, - pitch: f32, - scale: f32, -} - -impl CameraTransform3D { - fn new(view_box: RectF32) -> CameraTransform3D { - let scale = scale_factor_for_view_box(view_box); - CameraTransform3D { - position: Point3DF32::new( - 0.5 * view_box.max_x(), - -0.5 * view_box.max_y(), - 1.5 / scale, - 1.0, - ), - yaw: 0.0, - pitch: 0.0, - scale, - } - } - - fn offset(&mut self, vector: Point3DF32) -> bool { - let update = !vector.is_zero(); - if update { - let rotation = Transform3DF32::from_rotation(-self.yaw, -self.pitch, 0.0); - self.position = self.position + rotation.transform_point(vector); - } - update - } - - fn to_transform(&self) -> Transform3DF32 { - let mut transform = Transform3DF32::from_rotation(self.yaw, self.pitch, 0.0); - transform = transform.post_mul(&Transform3DF32::from_uniform_scale(2.0 * self.scale)); - transform = transform.post_mul(&Transform3DF32::from_translation( - -self.position.x(), - -self.position.y(), - -self.position.z(), - )); - - // Flip Y. - transform = transform.post_mul(&Transform3DF32::from_scale(1.0, -1.0, 1.0)); - - transform - } -} - -fn scale_factor_for_view_box(view_box: RectF32) -> f32 { - 1.0 / f32::min(view_box.size().x(), view_box.size().y()) -} - fn get_svg_building_message(built_svg: &BuiltSVG) -> String { if built_svg.result_flags.is_empty() { return String::new(); diff --git a/demo/common/src/ui.rs b/demo/common/src/ui.rs index 5b68161a..4409828d 100644 --- a/demo/common/src/ui.rs +++ b/demo/common/src/ui.rs @@ -8,8 +8,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use crate::camera::Mode; use crate::window::Window; -use crate::{BackgroundColor, Mode, Options}; +use crate::{BackgroundColor, Options}; use pathfinder_geometry::basic::point::Point2DI32; use pathfinder_geometry::basic::rect::RectI32; use pathfinder_gpu::resources::ResourceLoader;