Move the demo camera code into its own module

This commit is contained in:
Patrick Walton 2019-05-02 17:47:09 -07:00
parent 5e64876579
commit 29b137c81d
3 changed files with 210 additions and 184 deletions

195
demo/common/src/camera.rs Normal file
View File

@ -0,0 +1,195 @@
// pathfinder/demo/common/src/camera.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<OcularTransform>,
// 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())
}

View File

@ -13,16 +13,16 @@
#[macro_use] #[macro_use]
extern crate log; extern crate log;
use crate::camera::{Camera, Mode};
use crate::concurrent::DemoExecutor; use crate::concurrent::DemoExecutor;
use crate::device::{GroundProgram, GroundVertexArray}; use crate::device::{GroundProgram, GroundVertexArray};
use crate::ui::{DemoUI, UIAction}; 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 clap::{App, Arg};
use image::ColorType; 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::rect::RectF32;
use pathfinder_geometry::basic::transform2d::Transform2DF32; use pathfinder_geometry::basic::transform3d::Transform3DF32;
use pathfinder_geometry::basic::transform3d::{Perspective, Transform3DF32};
use pathfinder_geometry::color::{ColorF, ColorU}; use pathfinder_geometry::color::{ColorF, ColorU};
use pathfinder_gl::GLDevice; use pathfinder_gl::GLDevice;
use pathfinder_gpu::resources::ResourceLoader; 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_renderer::scene::Scene;
use pathfinder_svg::BuiltSVG; use pathfinder_svg::BuiltSVG;
use pathfinder_ui::{MousePosition, UIEvent}; use pathfinder_ui::{MousePosition, UIEvent};
use std::f32::consts::FRAC_PI_4;
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::Read;
use std::path::PathBuf; 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. // How much the scene is scaled when a zoom button is clicked.
const CAMERA_ZOOM_AMOUNT_2D: f32 = 0.1; 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 { const LIGHT_BG_COLOR: ColorU = ColorU {
r: 248, r: 248,
g: 248, g: 248,
@ -93,13 +89,11 @@ const APPROX_FONT_SIZE: f32 = 16.0;
const MESSAGE_TIMEOUT_SECS: u64 = 5; 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 const GRIDLINE_COUNT: i32 = 10;
pub mod window; pub mod window;
mod camera;
mod concurrent; mod concurrent;
mod device; mod device;
mod ui; mod ui;
@ -401,7 +395,8 @@ impl<W> DemoApp<W> where W: Window {
ref mut velocity, .. ref mut velocity, ..
} = self.camera } = 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); velocity.set_z(-CAMERA_VELOCITY / scale_factor);
self.dirty = true; self.dirty = true;
} }
@ -411,7 +406,8 @@ impl<W> DemoApp<W> where W: Window {
ref mut velocity, .. ref mut velocity, ..
} = self.camera } = 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); velocity.set_z(CAMERA_VELOCITY / scale_factor);
self.dirty = true; self.dirty = true;
} }
@ -421,7 +417,8 @@ impl<W> DemoApp<W> where W: Window {
ref mut velocity, .. ref mut velocity, ..
} = self.camera } = 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); velocity.set_x(-CAMERA_VELOCITY / scale_factor);
self.dirty = true; self.dirty = true;
} }
@ -431,7 +428,8 @@ impl<W> DemoApp<W> where W: Window {
ref mut velocity, .. ref mut velocity, ..
} = self.camera } = 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); velocity.set_x(CAMERA_VELOCITY / scale_factor);
self.dirty = true; 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)] #[derive(Clone, Copy, PartialEq)]
pub enum UIVisibility { pub enum UIVisibility {
None, None,
@ -1024,151 +999,6 @@ fn center_of_window(window_size: &WindowSize) -> Point2DF32 {
window_size.device_size().to_f32().scale(0.5) 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<OcularTransform>,
// 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 { fn get_svg_building_message(built_svg: &BuiltSVG) -> String {
if built_svg.result_flags.is_empty() { if built_svg.result_flags.is_empty() {
return String::new(); return String::new();

View File

@ -8,8 +8,9 @@
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // except according to those terms.
use crate::camera::Mode;
use crate::window::Window; use crate::window::Window;
use crate::{BackgroundColor, Mode, Options}; use crate::{BackgroundColor, Options};
use pathfinder_geometry::basic::point::Point2DI32; use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectI32; use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_gpu::resources::ResourceLoader; use pathfinder_gpu::resources::ResourceLoader;