├── screenshot.png ├── src ├── window │ ├── event.rs │ ├── points │ │ ├── shader.frag │ │ ├── mod.rs │ │ ├── source.rs │ │ ├── shader.vert │ │ ├── renderer.rs │ │ ├── point.rs │ │ └── builder.rs │ ├── camera │ │ ├── mod.rs │ │ ├── coord.rs │ │ ├── frustrum.rs │ │ ├── base.rs │ │ └── controller.rs │ ├── models │ │ ├── mod.rs │ │ ├── points.rs │ │ ├── matches.rs │ │ └── world.rs │ ├── lines │ │ ├── mod.rs │ │ ├── source.rs │ │ ├── line.rs │ │ ├── renderer.rs │ │ └── builder.rs │ ├── isometries │ │ ├── mod.rs │ │ ├── source.rs │ │ ├── builder.rs │ │ ├── renderer.rs │ │ └── isometry.rs │ ├── mod.rs │ ├── uniform.rs │ ├── builder.rs │ └── base.rs ├── engine │ ├── mod.rs │ ├── base.rs │ ├── event_loop.rs │ ├── timer.rs │ └── builder.rs ├── pipes │ ├── renderer.rs │ ├── mod.rs │ ├── vertex.rs │ ├── module.rs │ ├── builder.rs │ └── buffer.rs ├── lib.rs └── viewer.rs ├── .gitignore ├── examples ├── points.rs ├── matches.rs └── simple.rs ├── LICENSE ├── Cargo.toml └── README.md /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podo-os/slam-viewer/HEAD/screenshot.png -------------------------------------------------------------------------------- /src/window/event.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 2 | pub enum WindowEventState { 3 | Consumed, 4 | Unused, 5 | } 6 | -------------------------------------------------------------------------------- /src/engine/mod.rs: -------------------------------------------------------------------------------- 1 | mod base; 2 | mod builder; 3 | mod event_loop; 4 | mod timer; 5 | 6 | pub use self::base::Engine; 7 | pub use self::builder::EngineBuilder; 8 | -------------------------------------------------------------------------------- /src/pipes/renderer.rs: -------------------------------------------------------------------------------- 1 | pub trait PipelineRenderer { 2 | fn render<'a>(&'a mut self, device: &wgpu::Device, render_pass: &mut wgpu::RenderPass<'a>); 3 | } 4 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod engine; 2 | mod pipes; 3 | mod viewer; 4 | mod window; 5 | 6 | pub use viewer::alloc_thread; 7 | pub use window::{CameraControllerConfig, CameraFrustum}; 8 | -------------------------------------------------------------------------------- /src/window/points/shader.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location=0)in vec3 v_color; 4 | layout(location=0)out vec4 f_color; 5 | 6 | void main(){ 7 | f_color=vec4(v_color,1.); 8 | } 9 | -------------------------------------------------------------------------------- /src/window/camera/mod.rs: -------------------------------------------------------------------------------- 1 | mod base; 2 | mod controller; 3 | mod coord; 4 | mod frustrum; 5 | 6 | pub use self::base::Camera; 7 | pub use self::controller::{CameraController, CameraControllerConfig}; 8 | pub use self::frustrum::CameraFrustum; 9 | -------------------------------------------------------------------------------- /src/window/models/mod.rs: -------------------------------------------------------------------------------- 1 | mod points; 2 | mod world; 3 | 4 | #[cfg(feature = "rust-cv")] 5 | mod matches; 6 | 7 | pub use self::points::PointsModel; 8 | pub use self::world::WorldModel; 9 | 10 | #[cfg(feature = "rust-cv")] 11 | pub use self::matches::MatchesModel; 12 | -------------------------------------------------------------------------------- /src/window/lines/mod.rs: -------------------------------------------------------------------------------- 1 | mod builder; 2 | mod renderer; 3 | mod source; 4 | 5 | mod line; 6 | 7 | pub use self::builder::{build_render_pipeline, LinesBuilder}; 8 | pub use self::renderer::LinesRendener; 9 | pub use self::source::LineSource; 10 | 11 | pub use self::line::Line; 12 | -------------------------------------------------------------------------------- /src/window/isometries/mod.rs: -------------------------------------------------------------------------------- 1 | mod builder; 2 | mod renderer; 3 | mod source; 4 | 5 | mod isometry; 6 | 7 | pub use self::builder::IsometriesBuilder; 8 | pub use self::renderer::IsometriesRendener; 9 | pub use self::source::IsometrySource; 10 | 11 | pub use self::isometry::Isometry; 12 | -------------------------------------------------------------------------------- /src/window/points/mod.rs: -------------------------------------------------------------------------------- 1 | mod builder; 2 | mod renderer; 3 | mod source; 4 | 5 | mod point; 6 | 7 | pub use self::builder::{build_render_pipeline, PointsBuilder}; 8 | pub use self::renderer::PointsRendener; 9 | pub use self::source::PointSource; 10 | 11 | pub use self::point::Point; 12 | -------------------------------------------------------------------------------- /src/pipes/mod.rs: -------------------------------------------------------------------------------- 1 | mod buffer; 2 | mod builder; 3 | mod module; 4 | mod renderer; 5 | mod vertex; 6 | 7 | pub use self::buffer::GpuVec; 8 | pub use self::builder::{PipelineBuilder, PipelineDataBuilder}; 9 | pub use self::module::StaticShaderModule; 10 | pub use self::renderer::PipelineRenderer; 11 | pub use self::vertex::{GpuVertex, VertexFormat}; 12 | -------------------------------------------------------------------------------- /src/window/points/source.rs: -------------------------------------------------------------------------------- 1 | use crate::pipes::VertexFormat; 2 | 3 | use nalgebra::Point3; 4 | use slam_cv::{Colors, Number}; 5 | 6 | pub trait PointSource 7 | where 8 | N: 'static + Number, 9 | Point3: VertexFormat, 10 | { 11 | const COLOR: [f32; 3] = Colors::red(); 12 | 13 | fn collect_visual_points(&self) -> Vec>; 14 | } 15 | -------------------------------------------------------------------------------- /src/window/lines/source.rs: -------------------------------------------------------------------------------- 1 | use crate::pipes::VertexFormat; 2 | 3 | use nalgebra::Point3; 4 | use slam_cv::{Colors, Number}; 5 | 6 | pub trait LineSource 7 | where 8 | N: 'static + Number, 9 | Point3: VertexFormat, 10 | { 11 | const COLOR: [f32; 3] = Colors::blue(); 12 | 13 | fn collect_visual_lines(&self) -> Vec<[Point3; 2]>; 14 | } 15 | -------------------------------------------------------------------------------- /src/pipes/vertex.rs: -------------------------------------------------------------------------------- 1 | use slam_cv::Number; 2 | 3 | pub trait VertexFormat 4 | where 5 | N: Number, 6 | { 7 | fn format() -> wgpu::VertexFormat; 8 | } 9 | 10 | impl VertexFormat for nalgebra::Point3 { 11 | fn format() -> wgpu::VertexFormat { 12 | wgpu::VertexFormat::Float3 13 | } 14 | } 15 | 16 | pub trait GpuVertex { 17 | fn weight() -> u64; 18 | } 19 | -------------------------------------------------------------------------------- /src/window/points/shader.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location=0)in vec3 a_position; 4 | layout(location=1)in vec3 a_color; 5 | 6 | layout(location=0)out vec3 v_color; 7 | 8 | layout(set=0,binding=0)uniform Uniforms{ 9 | mat4 u_view_proj; 10 | }; 11 | 12 | void main(){ 13 | v_color=a_color; 14 | gl_PointSize=3.; 15 | gl_Position=u_view_proj*vec4(a_position,1.); 16 | } 17 | -------------------------------------------------------------------------------- /src/window/isometries/source.rs: -------------------------------------------------------------------------------- 1 | use crate::pipes::VertexFormat; 2 | 3 | use nalgebra::{Isometry3, Point3}; 4 | use slam_cv::{Colors, Number}; 5 | 6 | pub trait IsometrySource 7 | where 8 | N: 'static + Number, 9 | Point3: VertexFormat, 10 | { 11 | const COLOR: [f32; 3] = Colors::green(); 12 | const SIZE: [N; 2]; 13 | 14 | fn collect_visual_isometries(&self) -> Vec>; 15 | } 16 | -------------------------------------------------------------------------------- /src/engine/base.rs: -------------------------------------------------------------------------------- 1 | use std::thread; 2 | 3 | pub struct Engine { 4 | thread: thread::JoinHandle<()>, 5 | } 6 | 7 | impl Engine { 8 | pub(super) fn new(handle: F) -> Self 9 | where 10 | F: 'static + FnOnce() + Send, 11 | { 12 | Self { 13 | thread: thread::spawn(handle), 14 | } 15 | } 16 | 17 | pub fn wait(self) { 18 | self.thread.join().unwrap(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | # Generated by Cargo 4 | # will have compiled files and executables 5 | /target/ 6 | 7 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 8 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 9 | Cargo.lock 10 | 11 | # These are backup files generated by rustfmt 12 | **/*.rs.bk 13 | 14 | # Spir-V pre-compiled shaders 15 | **/*.spv 16 | 17 | # Editor 18 | /.vscode/ 19 | -------------------------------------------------------------------------------- /src/pipes/module.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | 3 | pub struct StaticShaderModule { 4 | pub spirv_source: &'static [u8], 5 | pub entry_point: Option<&'static str>, 6 | } 7 | 8 | impl StaticShaderModule { 9 | pub fn build(&self, device: &wgpu::Device) -> wgpu::ShaderModule { 10 | let data = wgpu::read_spirv(Cursor::new(self.spirv_source)).unwrap(); 11 | device.create_shader_module(&data) 12 | } 13 | 14 | pub fn entry_point(&self) -> &str { 15 | self.entry_point.unwrap_or("main") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/engine/event_loop.rs: -------------------------------------------------------------------------------- 1 | use winit::event_loop::EventLoop; 2 | 3 | #[cfg(target_os = "linux")] 4 | pub fn new_event_loop() -> EventLoop<()> { 5 | winit::platform::unix::EventLoopExtUnix::new_any_thread() 6 | } 7 | 8 | #[cfg(target_os = "windows")] 9 | pub fn new_event_loop() -> EventLoop<()> { 10 | winit::platform::windows::EventLoopExtWindows::new_any_thread() 11 | } 12 | 13 | #[cfg(target_arch = "wasm32")] 14 | pub fn new_event_loop() -> EventLoop<()> { 15 | unimplemented!("multi-threaded event loop in wasm32 is not supported yet") 16 | } 17 | -------------------------------------------------------------------------------- /src/window/mod.rs: -------------------------------------------------------------------------------- 1 | mod base; 2 | mod builder; 3 | mod camera; 4 | mod event; 5 | mod uniform; 6 | 7 | // Shaders, Pipelines 8 | mod lines; 9 | mod points; 10 | 11 | // Complex objects 12 | mod isometries; 13 | 14 | pub mod models; 15 | 16 | pub use self::base::Window; 17 | pub use self::builder::WindowBuilder; 18 | pub use self::camera::{CameraControllerConfig, CameraFrustum}; 19 | pub use self::event::WindowEventState; 20 | 21 | pub use self::lines::LineSource; 22 | pub use self::points::PointSource; 23 | 24 | pub use self::isometries::IsometrySource; 25 | -------------------------------------------------------------------------------- /src/window/camera/coord.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::{Unit, UnitQuaternion, Vector3}; 2 | use slam_cv::Number; 3 | 4 | #[derive(Clone, Copy, Debug)] 5 | pub struct CoordSystemRh 6 | where 7 | N: 'static + Number, 8 | { 9 | pub up_axis: Unit>, 10 | pub rotation_to_y_up: UnitQuaternion, 11 | } 12 | 13 | impl CoordSystemRh 14 | where 15 | N: 'static + Number, 16 | { 17 | #[inline] 18 | pub fn from_up_axis(up_axis: Unit>) -> Self { 19 | let pi = N::from(std::f32::consts::PI).unwrap(); 20 | 21 | let rotation_to_y_up = UnitQuaternion::rotation_between_axis(&up_axis, &Vector3::y_axis()) 22 | .unwrap_or_else(|| UnitQuaternion::from_axis_angle(&Vector3::x_axis(), pi)); 23 | Self { 24 | up_axis, 25 | rotation_to_y_up, 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/pipes/builder.rs: -------------------------------------------------------------------------------- 1 | use super::renderer::PipelineRenderer; 2 | use super::vertex::VertexFormat; 3 | use crate::window::WindowBuilder; 4 | 5 | use nalgebra::Point3; 6 | use slam_cv::Number; 7 | 8 | pub trait PipelineBuilder 9 | where 10 | Self: Send, 11 | N: 'static + Number, 12 | { 13 | fn build( 14 | self: Box, 15 | device: &wgpu::Device, 16 | texture_format: wgpu::TextureFormat, 17 | uniform_bind_group_layout: &wgpu::BindGroupLayout, 18 | ) -> Box; 19 | } 20 | 21 | pub trait PipelineDataBuilder 22 | where 23 | Self: Send, 24 | N: 'static + Number, 25 | Point3: VertexFormat, 26 | { 27 | type Builder: 'static + PipelineBuilder + Send; 28 | 29 | fn default_window(&self) -> WindowBuilder; 30 | 31 | fn build_data(self) -> Self::Builder; 32 | } 33 | -------------------------------------------------------------------------------- /src/engine/timer.rs: -------------------------------------------------------------------------------- 1 | use std::time; 2 | 3 | pub struct Timer { 4 | time: time::Instant, 5 | time_limit: time::Duration, 6 | } 7 | 8 | impl Timer { 9 | #[cfg(not(target_arch = "wasm32"))] 10 | pub fn try_new(time_limit: time::Duration) -> Option { 11 | Some(Self { 12 | time: std::time::Instant::now(), 13 | time_limit, 14 | }) 15 | } 16 | 17 | #[cfg(target_arch = "wasm32")] 18 | pub fn try_new(_time_limit: time::Duration) -> Option { 19 | None 20 | } 21 | 22 | pub fn sync(&mut self) { 23 | let elapsed = self.time.elapsed(); 24 | 25 | if elapsed < self.time_limit { 26 | let remain = self.time_limit - elapsed; 27 | 28 | std::thread::sleep(remain); 29 | } 30 | 31 | self.time = time::Instant::now(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/points.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::Point3; 2 | use rand_distr::{Distribution, StandardNormal}; 3 | 4 | fn main() { 5 | const NUM_POINTS: usize = 10_000; 6 | 7 | let mut rng = rand::thread_rng(); 8 | let mut rng = StandardNormal.sample_iter(&mut rng); 9 | 10 | let points = (0..NUM_POINTS) 11 | .map(|_| { 12 | let x = rng.next().unwrap(); 13 | let y = rng.next().unwrap(); 14 | let z = rng.next().unwrap(); 15 | Point3::new(x, y, z - 10.0) 16 | }) 17 | .collect(); 18 | 19 | #[cfg(target_arch = "wasm32")] 20 | { 21 | std::panic::set_hook(Box::new(console_error_panic_hook::hook)); 22 | console_log::init().expect("could not initialize logger"); 23 | } 24 | 25 | // make a window with this thread 26 | slam_viewer::alloc_thread().add_points(points).run(); 27 | } 28 | -------------------------------------------------------------------------------- /src/window/uniform.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Debug; 2 | 3 | use super::camera::Camera; 4 | 5 | use nalgebra::Matrix4; 6 | use slam_cv::Number; 7 | 8 | #[repr(C)] 9 | #[derive(Debug, Copy, Clone)] 10 | pub struct Uniforms 11 | where 12 | N: 'static + Number, 13 | { 14 | view_proj: Matrix4, 15 | } 16 | 17 | impl Default for Uniforms 18 | where 19 | N: 'static + Number, 20 | { 21 | fn default() -> Self { 22 | Self { 23 | view_proj: Matrix4::identity(), 24 | } 25 | } 26 | } 27 | 28 | impl Uniforms 29 | where 30 | N: 'static + Number, 31 | { 32 | pub fn update_view_proj(&mut self, camera: &Camera, aspect: N) { 33 | self.view_proj = camera.compute_view_proj(aspect); 34 | } 35 | } 36 | 37 | unsafe impl bytemuck::Pod for Uniforms where N: 'static + Number {} 38 | unsafe impl bytemuck::Zeroable for Uniforms where N: 'static + Number {} 39 | -------------------------------------------------------------------------------- /src/window/camera/frustrum.rs: -------------------------------------------------------------------------------- 1 | use super::base::Camera; 2 | use super::coord::CoordSystemRh; 3 | 4 | use nalgebra::{Point3, Vector3}; 5 | use slam_cv::Number; 6 | 7 | pub struct CameraFrustum 8 | where 9 | N: Number, 10 | { 11 | pub eye: Point3, 12 | pub at: Point3, 13 | 14 | pub fovy: N, 15 | pub znear: N, 16 | pub zfar: N, 17 | } 18 | 19 | impl Into> for CameraFrustum 20 | where 21 | N: Number, 22 | { 23 | fn into(self) -> Camera { 24 | let mut camera = Camera { 25 | eye: Vector3::zeros().into(), 26 | yaw: N::zero(), 27 | pitch: N::zero(), 28 | 29 | fovy: self.fovy, 30 | znear: self.znear, 31 | zfar: self.zfar, 32 | 33 | coord_system: CoordSystemRh::from_up_axis(Vector3::y_axis()), 34 | }; 35 | 36 | camera.look_at(Some(self.eye), self.at); 37 | 38 | camera 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/matches.rs: -------------------------------------------------------------------------------- 1 | use cv_core::FeatureMatch; 2 | use nalgebra::Point3; 3 | use rand_distr::{Distribution, StandardNormal}; 4 | 5 | fn main() { 6 | const NUM_POINTS: usize = 10_000; 7 | const SIGMA: f32 = 0.1; 8 | 9 | let mut rng = rand::thread_rng(); 10 | let mut rng = StandardNormal.sample_iter(&mut rng); 11 | 12 | let matches = (0..NUM_POINTS) 13 | .map(|_| { 14 | let x = rng.next().unwrap(); 15 | let y = rng.next().unwrap(); 16 | let z = rng.next().unwrap(); 17 | let p1 = Point3::new(x, y, z - 10.0); 18 | 19 | let x = x + rng.next().unwrap() * SIGMA; 20 | let y = y + rng.next().unwrap() * SIGMA; 21 | let z = z + rng.next().unwrap() * SIGMA; 22 | let p2 = Point3::new(x, y, z - 10.0); 23 | 24 | FeatureMatch(p1, p2) 25 | }) 26 | .collect(); 27 | 28 | #[cfg(target_arch = "wasm32")] 29 | { 30 | std::panic::set_hook(Box::new(console_error_panic_hook::hook)); 31 | console_log::init().expect("could not initialize logger"); 32 | } 33 | 34 | // make a window with this thread 35 | slam_viewer::alloc_thread().add_matches(matches).run(); 36 | } 37 | -------------------------------------------------------------------------------- /src/window/builder.rs: -------------------------------------------------------------------------------- 1 | use super::base::Window; 2 | use super::camera::{CameraControllerConfig, CameraFrustum}; 3 | use crate::pipes::{PipelineBuilder, VertexFormat}; 4 | 5 | use nalgebra::Point3; 6 | use slam_cv::Number; 7 | use winit::{event_loop::EventLoop, window}; 8 | 9 | pub struct WindowBuilder 10 | where 11 | N: 'static + Number, 12 | Point3: VertexFormat, 13 | { 14 | pub title: Option, 15 | pub framerate: Option, 16 | 17 | pub camera: CameraFrustum, 18 | pub camera_controller: CameraControllerConfig, 19 | } 20 | 21 | impl WindowBuilder 22 | where 23 | N: 'static + Number, 24 | Point3: VertexFormat, 25 | { 26 | pub async fn build( 27 | self, 28 | event_loop: &EventLoop, 29 | pipeline_builder: Box>, 30 | ) -> (window::WindowId, Window) { 31 | let window = window::WindowBuilder::new().build(&event_loop).unwrap(); 32 | let id = window.id(); 33 | 34 | if let Some(title) = &self.title { 35 | window.set_title(title); 36 | } 37 | 38 | let engine_window = Window::new(window, self, pipeline_builder).await; 39 | (id, engine_window) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/window/lines/line.rs: -------------------------------------------------------------------------------- 1 | use super::super::points::Point; 2 | use crate::pipes::{GpuVertex, VertexFormat}; 3 | 4 | use nalgebra::Point3; 5 | use slam_cv::Number; 6 | 7 | #[repr(C)] 8 | #[derive(Copy, Clone, Debug, Default)] 9 | pub struct Line 10 | where 11 | N: 'static + Number, 12 | Point3: VertexFormat, 13 | { 14 | pub start: Point, 15 | pub end: Point, 16 | } 17 | 18 | type Attributes = [wgpu::VertexAttributeDescriptor; 2]; 19 | 20 | impl Line 21 | where 22 | N: 'static + Number, 23 | Point3: VertexFormat, 24 | { 25 | pub fn attributes() -> Attributes { 26 | Point::attributes() 27 | } 28 | 29 | pub fn desc(attributes: &Attributes) -> wgpu::VertexBufferDescriptor<'_> { 30 | Point::desc(attributes) 31 | } 32 | } 33 | 34 | impl GpuVertex for Line 35 | where 36 | N: 'static + Number, 37 | Point3: VertexFormat, 38 | { 39 | fn weight() -> u64 { 40 | 2 41 | } 42 | } 43 | 44 | unsafe impl bytemuck::Pod for Line 45 | where 46 | N: 'static + Number, 47 | Point3: VertexFormat, 48 | { 49 | } 50 | unsafe impl bytemuck::Zeroable for Line 51 | where 52 | N: 'static + Number, 53 | Point3: VertexFormat, 54 | { 55 | } 56 | -------------------------------------------------------------------------------- /src/window/isometries/builder.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | 3 | use super::super::lines::build_render_pipeline; 4 | use super::renderer::IsometriesRendener; 5 | use super::source::IsometrySource; 6 | use crate::pipes::{GpuVec, VertexFormat}; 7 | 8 | use nalgebra::Point3; 9 | use slam_cv::Number; 10 | 11 | pub struct IsometriesBuilder 12 | where 13 | N: 'static + Number, 14 | Point3: VertexFormat, 15 | S: IsometrySource, 16 | { 17 | pub source: S, 18 | 19 | number: PhantomData, 20 | } 21 | 22 | impl IsometriesBuilder 23 | where 24 | N: 'static + Number, 25 | Point3: VertexFormat, 26 | S: IsometrySource, 27 | { 28 | pub fn new(source: S) -> Self { 29 | Self { 30 | source, 31 | 32 | number: Default::default(), 33 | } 34 | } 35 | 36 | pub fn build( 37 | self, 38 | device: &wgpu::Device, 39 | texture_format: wgpu::TextureFormat, 40 | uniform_bind_group_layout: &wgpu::BindGroupLayout, 41 | ) -> IsometriesRendener { 42 | let render_pipeline = 43 | build_render_pipeline(device, texture_format, uniform_bind_group_layout); 44 | 45 | IsometriesRendener { 46 | render_pipeline, 47 | buffer: GpuVec::new(wgpu::BufferUsage::VERTEX), 48 | 49 | number: Default::default(), 50 | source: self.source, 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020, Ho Kim 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the author nor the names of its contributors may be used 15 | to endorse or promote products derived from this software without specific 16 | prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "slam-viewer" 3 | version = "0.1.4" 4 | authors = ["h "] 5 | edition = "2018" 6 | 7 | description = "Simple wgpu based SLAM map viewer." 8 | homepage = "https://github.com/podo-os" 9 | readme = "README.md" 10 | license = "BSD-3-Clause" 11 | 12 | autoexamples = true 13 | 14 | include = [ 15 | "src/**/*.rs", 16 | "src/**/*.vert", 17 | "src/**/*.vert.spv", 18 | "src/**/*.frag", 19 | "src/**/*.frag.spv", 20 | "examples/**/*.rs", 21 | "examples/Cargo.toml", 22 | "Cargo.toml", 23 | "LICENSE", 24 | "README.md" 25 | ] 26 | 27 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 28 | 29 | [features] 30 | default = ["rust-cv"] 31 | 32 | rust-cv = ["cv-core", "slam-cv/cv-core"] 33 | 34 | [build-dependencies] 35 | glob = "0.3" 36 | glsl-to-spirv = "0.1" 37 | 38 | [dependencies] 39 | bytemuck = "1.2" 40 | failure = "0.1" 41 | futures = "0.3" 42 | image = "0.23" 43 | nalgebra = "0.21" 44 | num = "0.2" 45 | slam-cv = "0.1" 46 | wgpu = { git = "https://github.com/gfx-rs/wgpu-rs" } 47 | winit = { version = "0.22", features = ["web-sys"] } 48 | 49 | cv-core = { version = "0.10", optional = true } 50 | 51 | [target.'cfg(target_arch = "wasm32")'.dependencies] 52 | wasm-bindgen-futures = "0.4" 53 | web-sys = "0.3" 54 | 55 | [dev-dependencies] 56 | rand = { version = "0.7", features = ["wasm-bindgen"] } 57 | rand_distr = "0.2" 58 | 59 | [target.'cfg(target_arch = "wasm32")'.dev-dependencies] 60 | console_error_panic_hook = "0.1" 61 | console_log = "0.2" 62 | 63 | [profile.dev] 64 | # opt-level = 3 65 | -------------------------------------------------------------------------------- /src/window/points/renderer.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | 3 | use super::builder::PointsBuilder; 4 | use super::point::Point; 5 | use super::source::PointSource; 6 | use crate::pipes::{GpuVec, PipelineBuilder, PipelineRenderer, VertexFormat}; 7 | 8 | use nalgebra::Point3; 9 | use slam_cv::Number; 10 | 11 | pub struct PointsRendener 12 | where 13 | N: 'static + Number, 14 | Point3: VertexFormat, 15 | S: 'static + PointSource, 16 | { 17 | pub render_pipeline: wgpu::RenderPipeline, 18 | pub buffer: GpuVec>, 19 | 20 | pub number: PhantomData, 21 | pub source: S, 22 | } 23 | 24 | impl PipelineBuilder for PointsBuilder 25 | where 26 | Self: Send, 27 | N: 'static + Number, 28 | Point3: VertexFormat, 29 | S: 'static + PointSource, 30 | { 31 | fn build( 32 | self: Box, 33 | device: &wgpu::Device, 34 | texture_format: wgpu::TextureFormat, 35 | uniform_bind_group_layout: &wgpu::BindGroupLayout, 36 | ) -> Box { 37 | Box::new((*self).build(device, texture_format, uniform_bind_group_layout)) 38 | } 39 | } 40 | 41 | impl PipelineRenderer for PointsRendener 42 | where 43 | N: 'static + Number, 44 | Point3: VertexFormat, 45 | S: PointSource, 46 | { 47 | fn render<'a>(&'a mut self, device: &wgpu::Device, render_pass: &mut wgpu::RenderPass<'a>) { 48 | render_pass.set_pipeline(&self.render_pipeline); 49 | 50 | let color = S::COLOR.into(); 51 | 52 | let points = self 53 | .source 54 | .collect_visual_points() 55 | .into_iter() 56 | .map(|p| Point { position: p, color }) 57 | .collect(); 58 | 59 | self.buffer.update(device, points); 60 | self.buffer.set_buffer(render_pass); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/pipes/buffer.rs: -------------------------------------------------------------------------------- 1 | use super::vertex::GpuVertex; 2 | 3 | pub struct GpuVec 4 | where 5 | D: bytemuck::Pod + bytemuck::Zeroable + GpuVertex, 6 | { 7 | cpu_vec: Option>, 8 | 9 | gpu_buffer: Option, 10 | gpu_buffer_size: u64, 11 | 12 | usage: wgpu::BufferUsage, 13 | } 14 | 15 | impl GpuVec 16 | where 17 | D: bytemuck::Pod + bytemuck::Zeroable + GpuVertex, 18 | { 19 | pub fn new(usage: wgpu::BufferUsage) -> Self { 20 | Self { 21 | cpu_vec: None, 22 | 23 | gpu_buffer: None, 24 | gpu_buffer_size: 0, 25 | 26 | usage, 27 | } 28 | } 29 | 30 | pub fn update(&mut self, device: &wgpu::Device, vec: Vec) { 31 | self.cpu_vec = Some(vec); 32 | self.update_buffer(device); 33 | } 34 | 35 | pub fn set_buffer<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { 36 | if let Some(buffer) = &self.gpu_buffer { 37 | let data_size = core::mem::size_of::() as u64; 38 | let gpu_buffer_size = self.gpu_buffer_size * data_size; 39 | let vec_len = (D::weight() * self.gpu_buffer_size) as u32; 40 | 41 | render_pass.set_vertex_buffer(0, buffer, 0, gpu_buffer_size); 42 | render_pass.draw(0..vec_len, 0..1); 43 | } 44 | } 45 | 46 | fn update_buffer(&mut self, device: &wgpu::Device) { 47 | if let Some(cpu_vec) = &self.cpu_vec { 48 | let size = cpu_vec.len() as u64; 49 | if size > self.gpu_buffer_size { 50 | let cpu_data = bytemuck::cast_slice(&cpu_vec); 51 | self.gpu_buffer = Some(device.create_buffer_with_data(cpu_data, self.usage)); 52 | self.gpu_buffer_size = size; 53 | } else { 54 | // TODO: more efficient write 55 | // FIXME: to be implemented 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/window/isometries/renderer.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | 3 | use super::builder::IsometriesBuilder; 4 | use super::isometry::Isometry; 5 | use super::source::IsometrySource; 6 | use crate::pipes::{GpuVec, PipelineBuilder, PipelineRenderer, VertexFormat}; 7 | 8 | use nalgebra::Point3; 9 | use slam_cv::Number; 10 | 11 | pub struct IsometriesRendener 12 | where 13 | N: 'static + Number, 14 | Point3: VertexFormat, 15 | S: 'static + IsometrySource, 16 | { 17 | pub render_pipeline: wgpu::RenderPipeline, 18 | pub buffer: GpuVec>, 19 | 20 | pub number: PhantomData, 21 | pub source: S, 22 | } 23 | 24 | impl PipelineBuilder for IsometriesBuilder 25 | where 26 | Self: Send, 27 | N: 'static + Number, 28 | Point3: VertexFormat, 29 | S: 'static + IsometrySource, 30 | { 31 | fn build( 32 | self: Box, 33 | device: &wgpu::Device, 34 | texture_format: wgpu::TextureFormat, 35 | uniform_bind_group_layout: &wgpu::BindGroupLayout, 36 | ) -> Box { 37 | Box::new((*self).build(device, texture_format, uniform_bind_group_layout)) 38 | } 39 | } 40 | 41 | impl PipelineRenderer for IsometriesRendener 42 | where 43 | N: 'static + Number, 44 | Point3: VertexFormat, 45 | S: IsometrySource, 46 | { 47 | fn render<'a>(&'a mut self, device: &wgpu::Device, render_pass: &mut wgpu::RenderPass<'a>) { 48 | render_pass.set_pipeline(&self.render_pipeline); 49 | 50 | let size = S::SIZE.into(); 51 | let color = S::COLOR.into(); 52 | 53 | let isometries = self 54 | .source 55 | .collect_visual_isometries() 56 | .into_iter() 57 | .map(|i| Isometry::from_iso(i, size, color)) 58 | .collect(); 59 | 60 | self.buffer.update(device, isometries); 61 | self.buffer.set_buffer(render_pass); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/window/lines/renderer.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | 3 | use super::super::points::Point; 4 | use super::builder::LinesBuilder; 5 | use super::line::Line; 6 | use super::source::LineSource; 7 | use crate::pipes::{GpuVec, PipelineBuilder, PipelineRenderer, VertexFormat}; 8 | 9 | use nalgebra::Point3; 10 | use slam_cv::Number; 11 | 12 | pub struct LinesRendener 13 | where 14 | N: 'static + Number, 15 | Point3: VertexFormat, 16 | S: 'static + LineSource, 17 | { 18 | pub render_pipeline: wgpu::RenderPipeline, 19 | pub buffer: GpuVec>, 20 | 21 | pub number: PhantomData, 22 | pub source: S, 23 | } 24 | 25 | impl PipelineBuilder for LinesBuilder 26 | where 27 | Self: Send, 28 | N: 'static + Number, 29 | Point3: VertexFormat, 30 | S: 'static + LineSource, 31 | { 32 | fn build( 33 | self: Box, 34 | device: &wgpu::Device, 35 | texture_format: wgpu::TextureFormat, 36 | uniform_bind_group_layout: &wgpu::BindGroupLayout, 37 | ) -> Box { 38 | Box::new((*self).build(device, texture_format, uniform_bind_group_layout)) 39 | } 40 | } 41 | 42 | impl PipelineRenderer for LinesRendener 43 | where 44 | N: 'static + Number, 45 | Point3: VertexFormat, 46 | S: LineSource, 47 | { 48 | fn render<'a>(&'a mut self, device: &wgpu::Device, render_pass: &mut wgpu::RenderPass<'a>) { 49 | render_pass.set_pipeline(&self.render_pipeline); 50 | 51 | let color = S::COLOR.into(); 52 | 53 | let lines = self 54 | .source 55 | .collect_visual_lines() 56 | .into_iter() 57 | .map(|[p1, p2]| Line { 58 | start: Point { 59 | position: p1, 60 | color, 61 | }, 62 | end: Point { 63 | position: p2, 64 | color, 65 | }, 66 | }) 67 | .collect(); 68 | 69 | self.buffer.update(device, lines); 70 | self.buffer.set_buffer(render_pass); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/window/points/point.rs: -------------------------------------------------------------------------------- 1 | use core::mem; 2 | 3 | use crate::pipes::{GpuVertex, VertexFormat}; 4 | 5 | use nalgebra::Point3; 6 | use slam_cv::{Colors, Number}; 7 | 8 | #[repr(C)] 9 | #[derive(Copy, Clone, Debug)] 10 | pub struct Point 11 | where 12 | N: 'static + Number, 13 | Point3: VertexFormat, 14 | { 15 | pub position: Point3, 16 | pub color: Point3, 17 | } 18 | 19 | type Attributes = [wgpu::VertexAttributeDescriptor; 2]; 20 | 21 | impl Default for Point 22 | where 23 | N: 'static + Number, 24 | Point3: VertexFormat, 25 | { 26 | fn default() -> Self { 27 | Self { 28 | position: Point3::new(N::zero(), N::zero(), N::zero()), 29 | color: Colors::red().into(), 30 | } 31 | } 32 | } 33 | 34 | impl Point 35 | where 36 | N: 'static + Number, 37 | Point3: VertexFormat, 38 | { 39 | pub fn attributes() -> Attributes { 40 | [ 41 | wgpu::VertexAttributeDescriptor { 42 | offset: 0, 43 | shader_location: 0, 44 | format: Point3::::format(), 45 | }, 46 | wgpu::VertexAttributeDescriptor { 47 | offset: mem::size_of::>() as wgpu::BufferAddress, 48 | shader_location: 1, 49 | format: Point3::::format(), 50 | }, 51 | ] 52 | } 53 | 54 | pub fn desc(attributes: &Attributes) -> wgpu::VertexBufferDescriptor<'_> { 55 | wgpu::VertexBufferDescriptor { 56 | stride: mem::size_of::() as wgpu::BufferAddress, 57 | step_mode: wgpu::InputStepMode::Vertex, 58 | attributes, 59 | } 60 | } 61 | } 62 | 63 | impl GpuVertex for Point 64 | where 65 | N: 'static + Number, 66 | Point3: VertexFormat, 67 | { 68 | fn weight() -> u64 { 69 | 1 70 | } 71 | } 72 | 73 | unsafe impl bytemuck::Pod for Point 74 | where 75 | N: 'static + Number, 76 | Point3: VertexFormat, 77 | { 78 | } 79 | unsafe impl bytemuck::Zeroable for Point 80 | where 81 | N: 'static + Number, 82 | Point3: VertexFormat, 83 | { 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SLAM Viewer 2 | 3 | ![demo](screenshot.png) 4 | 5 | Simple [wgpu](https://github.com/gfx-rs/wgpu-rs) based SLAM map viewer. 6 | 7 | ## Current Working 8 | 9 | * [x] Draw points 10 | * [x] Mouse control 11 | * [x] Draw frames 12 | * [ ] Draw input images 13 | * [ ] UI (Buttons, ...) 14 | 15 | ## Usage 16 | 17 | Add this to your `Cargo.toml`: 18 | 19 | ```toml 20 | [dependencies] 21 | slam-cv = "0.1" 22 | slam-viewer = "0.1" 23 | ``` 24 | 25 | If you are new to [slam-cv](https://github.com/podo-os/slam-cv), enter this command simply at your prompt: 26 | 27 | ```sh 28 | git clone https://github.com/podo-os/slam-viewer 29 | cd slam-viewer 30 | cargo run --example simple 31 | ``` 32 | 33 | If you have your own `World`, add this to your `main.rs`: 34 | 35 | ```rust 36 | fn main() { 37 | let world = MyWorld(..); 38 | 39 | slam_viewer::alloc_thread().add(world).run(); 40 | } 41 | ``` 42 | 43 | ### Run Examples on the Web (`wasm32-unknown-unknown`) 44 | 45 | The manual is taken from `wgpu-rs`. 46 | Note that running on the web is quite unstable and only available in `Windows`. 47 | For more information, please visit [the official `wgpu-rs` repository](https://github.com/gfx-rs/wgpu-rs). 48 | 49 | To run examples on the `wasm32-unknown-unknown` target, first build the example as usual, then run `wasm-bindgen`: 50 | 51 | ```bash 52 | # Install or update wasm-bindgen-cli 53 | cargo install -f wasm-bindgen-cli 54 | # Build with the wasm target 55 | RUSTFLAGS=--cfg=web_sys_unstable_apis cargo build --target wasm32-unknown-unknown --example simple 56 | # Generate bindings in a `target/generated` directory 57 | wasm-bindgen --out-dir target/generated --web target/wasm32-unknown-unknown/debug/examples/simple.wasm 58 | ``` 59 | 60 | Create an `index.html` file into `target/generated` directory and add the following code: 61 | 62 | ```html 63 | 64 | 65 | 66 | 67 | 68 | 69 | 76 | 77 | 78 | ``` 79 | 80 | Now run a web server locally inside the `target/generated` directory to see the `simple` in the browser. 81 | e.g. `python -m http.server` 82 | 83 | ## Reference 84 | 85 | * `wgpu` tutorial: https://sotrh.github.io/learn-wgpu/ 86 | * `kiss` 3d graphics engine: https://github.com/sebcrozet/kiss3d 87 | -------------------------------------------------------------------------------- /src/viewer.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | engine::{Engine, EngineBuilder}, 3 | pipes::{PipelineBuilder, PipelineDataBuilder, VertexFormat}, 4 | window::{models, IsometrySource, LineSource, PointSource, WindowBuilder}, 5 | }; 6 | 7 | use nalgebra::{allocator::Allocator, DefaultAllocator, DimName, Point, Point3}; 8 | use slam_cv::prelude::*; 9 | 10 | /// **caution**: This function can only be called once per process. 11 | pub fn alloc_thread() -> Viewer 12 | where 13 | N: 'static + Number, 14 | Point3: VertexFormat, 15 | { 16 | Viewer { windows: vec![] } 17 | } 18 | 19 | pub struct Viewer 20 | where 21 | N: 'static + Number, 22 | Point3: VertexFormat, 23 | { 24 | windows: Vec<(WindowBuilder, Box>)>, 25 | } 26 | 27 | impl Viewer 28 | where 29 | N: 'static + Number, 30 | Point3: VertexFormat, 31 | { 32 | pub fn add_world(self, world: W) -> Self 33 | where 34 | F: 'static + Landmark + Clone, 35 | KF: 'static + KeyFrame + Clone, 36 | W: 'static + World + Clone, 37 | models::WorldModel: 38 | PipelineDataBuilder + PointSource + LineSource + IsometrySource, 39 | { 40 | self.add(models::WorldModel::new(world)) 41 | } 42 | 43 | pub fn add_points(self, points: Vec>) -> Self 44 | where 45 | D: DimName, 46 | DefaultAllocator: Allocator, 47 | models::PointsModel: PipelineBuilder + PipelineDataBuilder + PointSource, 48 | { 49 | self.add(models::PointsModel::new(points)) 50 | } 51 | 52 | #[cfg(feature = "rust-cv")] 53 | pub fn add_matches(self, matches: Vec>>) -> Self 54 | where 55 | D: DimName, 56 | DefaultAllocator: Allocator, 57 | models::MatchesModel: 58 | PipelineBuilder + PipelineDataBuilder + PointSource + LineSource, 59 | { 60 | self.add(models::MatchesModel::new(matches)) 61 | } 62 | } 63 | 64 | impl Viewer 65 | where 66 | N: 'static + Number, 67 | Point3: VertexFormat, 68 | { 69 | fn add(self, data: D) -> Self 70 | where 71 | D: 'static + PipelineBuilder + PipelineDataBuilder, 72 | { 73 | self.add_window_pipe(data.default_window(), data) 74 | } 75 | 76 | fn add_window_pipe

(mut self, window: WindowBuilder, pipe: P) -> Self 77 | where 78 | P: 'static + PipelineBuilder, 79 | { 80 | self.windows.push((window, Box::new(pipe))); 81 | self 82 | } 83 | 84 | pub fn run(self) { 85 | self.compile().run() 86 | } 87 | 88 | /// TODO: cross-platform compatibility 89 | pub fn spawn(self) -> Engine { 90 | self.compile().spawn() 91 | } 92 | 93 | fn compile(self) -> EngineBuilder { 94 | EngineBuilder { 95 | windows: self.windows, 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/window/models/points.rs: -------------------------------------------------------------------------------- 1 | use super::super::builder::WindowBuilder; 2 | use super::super::camera::{CameraControllerConfig, CameraFrustum}; 3 | use super::super::points::{PointSource, PointsBuilder}; 4 | use crate::pipes::{PipelineBuilder, PipelineDataBuilder, PipelineRenderer}; 5 | 6 | use nalgebra::{base::allocator::Allocator, DefaultAllocator, DimName, Point, Point3, U2, U3}; 7 | use slam_cv::Number; 8 | 9 | pub struct PointsModel 10 | where 11 | N: 'static + Number, 12 | D: DimName, 13 | DefaultAllocator: Allocator, 14 | { 15 | points: Vec>, 16 | } 17 | 18 | impl PointsModel 19 | where 20 | N: 'static + Number, 21 | D: DimName, 22 | DefaultAllocator: Allocator, 23 | { 24 | pub fn new(points: Vec>) -> Self { 25 | Self { points } 26 | } 27 | } 28 | 29 | impl PipelineDataBuilder for PointsModel 30 | where 31 | Self: PipelineBuilder + Send, 32 | D: DimName, 33 | DefaultAllocator: Allocator, 34 | { 35 | type Builder = Self; 36 | 37 | fn default_window(&self) -> WindowBuilder { 38 | WindowBuilder { 39 | title: Some("2d Points Viewer".to_string()), 40 | framerate: Some(120), 41 | 42 | camera: CameraFrustum { 43 | eye: Point3::new(0., 2., 5.), 44 | at: Point3::new(0., 0., 0.), 45 | 46 | fovy: std::f32::consts::FRAC_PI_4, 47 | znear: 0.1, 48 | zfar: 100.0, 49 | }, 50 | camera_controller: CameraControllerConfig::default(), 51 | } 52 | } 53 | 54 | fn build_data(self) -> Self::Builder { 55 | self 56 | } 57 | } 58 | 59 | impl PipelineBuilder for PointsModel 60 | where 61 | Self: PointSource + Send, 62 | N: 'static + Number, 63 | D: DimName, 64 | DefaultAllocator: Allocator, 65 | { 66 | fn build( 67 | self: Box, 68 | device: &wgpu::Device, 69 | texture_format: wgpu::TextureFormat, 70 | uniform_bind_group_layout: &wgpu::BindGroupLayout, 71 | ) -> Box { 72 | let points = *self; 73 | Box::new(PointsBuilder::new(points).build( 74 | device, 75 | texture_format, 76 | uniform_bind_group_layout, 77 | )) 78 | } 79 | } 80 | 81 | impl PointSource for PointsModel { 82 | fn collect_visual_points(&self) -> Vec> { 83 | self.points 84 | .iter() 85 | .map(|p| Point3::new(p.x, p.y, 0.0)) 86 | .collect() 87 | } 88 | } 89 | 90 | impl PointSource for PointsModel { 91 | fn collect_visual_points(&self) -> Vec> { 92 | self.points 93 | .iter() 94 | .map(|p| Point3::new(p.x as f32, p.y as f32, 0.0)) 95 | .collect() 96 | } 97 | } 98 | 99 | impl PointSource for PointsModel { 100 | fn collect_visual_points(&self) -> Vec> { 101 | self.points.clone() 102 | } 103 | } 104 | 105 | impl PointSource for PointsModel { 106 | fn collect_visual_points(&self) -> Vec> { 107 | self.points 108 | .iter() 109 | .map(|p| Point3::new(p.x as f32, p.y as f32, p.z as f32)) 110 | .collect() 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/window/camera/base.rs: -------------------------------------------------------------------------------- 1 | //! Many of this code is from [kiss3d](https://github.com/sebcrozet/kiss3d) 2 | //! https://github.com/sebcrozet/kiss3d/blob/master/src/camera/first_person.rs 3 | 4 | use super::coord::CoordSystemRh; 5 | 6 | use nalgebra::{Isometry3, Matrix4, Perspective3, Point3, Vector2, Vector3}; 7 | use num::Float; 8 | use slam_cv::Number; 9 | 10 | #[derive(Debug)] 11 | pub struct Camera 12 | where 13 | N: Number, 14 | { 15 | pub eye: Point3, 16 | pub yaw: N, 17 | pub pitch: N, 18 | 19 | pub fovy: N, 20 | pub znear: N, 21 | pub zfar: N, 22 | 23 | pub coord_system: CoordSystemRh, 24 | } 25 | 26 | impl Camera 27 | where 28 | N: Number, 29 | { 30 | /// Changes the orientation and position of the camera to look at the specified point. 31 | pub fn look_at(&mut self, eye: Option>, at: Point3) { 32 | let eye = eye.or_else(|| Some(self.eye)).unwrap(); 33 | let dist = (eye - at).norm(); 34 | 35 | let view_eye = self.coord_system.rotation_to_y_up * eye; 36 | let view_at = self.coord_system.rotation_to_y_up * at; 37 | let pitch = Float::acos((view_at.y - view_eye.y) / dist); 38 | let yaw = Float::atan2(view_at.z - view_eye.z, view_at.x - view_eye.x); 39 | 40 | self.eye = eye; 41 | self.yaw = yaw; 42 | self.pitch = pitch; 43 | } 44 | 45 | pub fn compute_view_proj(&self, aspect: N) -> Matrix4 { 46 | let proj = Perspective3::new(aspect, self.fovy, self.znear, self.zfar); 47 | 48 | let view = self.view_transform().to_homogeneous(); 49 | 50 | proj.as_matrix() * view 51 | } 52 | 53 | pub fn rotate(&mut self, dpos: Vector2) { 54 | self.yaw += dpos.x; 55 | self.pitch += dpos.y; 56 | 57 | self.update_restrictions(); 58 | } 59 | 60 | pub fn move_to(&mut self, dpos: Vector2) { 61 | let at = self.at(); 62 | let dir = (at - self.eye).normalize(); 63 | let tangent = self.coord_system.up_axis.cross(&dir).normalize(); 64 | let bitangent = dir.cross(&tangent); 65 | 66 | self.eye += tangent * dpos.x + bitangent * dpos.y; 67 | } 68 | 69 | pub fn scale(&mut self, yoff: N) { 70 | let front = self.observer_frame() * Vector3::z(); 71 | 72 | self.eye += front * yoff; 73 | } 74 | 75 | /// The point the camera is looking at. 76 | fn at(&self) -> Point3 { 77 | let view_eye = self.coord_system.rotation_to_y_up * self.eye; 78 | let ax = view_eye.x + Float::cos(self.yaw) * Float::sin(self.pitch); 79 | let ay = view_eye.y + Float::cos(self.pitch); 80 | let az = view_eye.z + Float::sin(self.yaw) * Float::sin(self.pitch); 81 | self.coord_system.rotation_to_y_up.inverse() * Point3::new(ax, ay, az) 82 | } 83 | 84 | /// The camera view transformation (i-e transformation without projection). 85 | fn view_transform(&self) -> Isometry3 { 86 | Isometry3::look_at_rh(&self.eye, &self.at(), &self.coord_system.up_axis) 87 | } 88 | 89 | /// The camera observer local frame. 90 | fn observer_frame(&self) -> Isometry3 { 91 | Isometry3::face_towards(&self.eye, &self.at(), &self.coord_system.up_axis) 92 | } 93 | 94 | fn update_restrictions(&mut self) { 95 | let thr = N::from(0.01).unwrap(); 96 | 97 | if self.pitch <= thr { 98 | self.pitch = thr; 99 | } 100 | 101 | let pi = N::from(std::f32::consts::PI).unwrap(); 102 | if self.pitch > pi - thr { 103 | self.pitch = pi - thr; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/window/points/builder.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | 3 | use super::point::Point; 4 | use super::renderer::PointsRendener; 5 | use super::source::PointSource; 6 | use crate::pipes::{GpuVec, StaticShaderModule, VertexFormat}; 7 | 8 | use nalgebra::Point3; 9 | use slam_cv::Number; 10 | 11 | pub struct PointsBuilder 12 | where 13 | N: 'static + Number, 14 | Point3: VertexFormat, 15 | S: PointSource, 16 | { 17 | pub source: S, 18 | 19 | number: PhantomData, 20 | } 21 | 22 | impl PointsBuilder 23 | where 24 | N: 'static + Number, 25 | Point3: VertexFormat, 26 | S: PointSource, 27 | { 28 | pub fn new(source: S) -> Self { 29 | Self { 30 | source, 31 | 32 | number: Default::default(), 33 | } 34 | } 35 | 36 | pub fn build( 37 | self, 38 | device: &wgpu::Device, 39 | texture_format: wgpu::TextureFormat, 40 | uniform_bind_group_layout: &wgpu::BindGroupLayout, 41 | ) -> PointsRendener { 42 | let render_pipeline = 43 | build_render_pipeline(device, texture_format, uniform_bind_group_layout); 44 | 45 | PointsRendener { 46 | render_pipeline, 47 | buffer: GpuVec::new(wgpu::BufferUsage::VERTEX), 48 | 49 | number: Default::default(), 50 | source: self.source, 51 | } 52 | } 53 | } 54 | 55 | pub fn build_render_pipeline( 56 | device: &wgpu::Device, 57 | texture_format: wgpu::TextureFormat, 58 | uniform_bind_group_layout: &wgpu::BindGroupLayout, 59 | ) -> wgpu::RenderPipeline { 60 | const VS_SRC: StaticShaderModule = StaticShaderModule { 61 | spirv_source: include_bytes!("shader.vert.spv"), 62 | entry_point: None, 63 | }; 64 | const FS_SRC: StaticShaderModule = StaticShaderModule { 65 | spirv_source: include_bytes!("shader.frag.spv"), 66 | entry_point: None, 67 | }; 68 | 69 | let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { 70 | bind_group_layouts: &[&uniform_bind_group_layout], 71 | }); 72 | 73 | device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { 74 | layout: &render_pipeline_layout, 75 | vertex_stage: wgpu::ProgrammableStageDescriptor { 76 | module: &VS_SRC.build(device), 77 | entry_point: VS_SRC.entry_point(), 78 | }, 79 | fragment_stage: Some(wgpu::ProgrammableStageDescriptor { 80 | module: &FS_SRC.build(device), 81 | entry_point: FS_SRC.entry_point(), 82 | }), 83 | rasterization_state: Some(wgpu::RasterizationStateDescriptor { 84 | front_face: wgpu::FrontFace::Ccw, 85 | cull_mode: wgpu::CullMode::Back, 86 | depth_bias: 0, 87 | depth_bias_slope_scale: 0.0, 88 | depth_bias_clamp: 0.0, 89 | }), 90 | color_states: &[wgpu::ColorStateDescriptor { 91 | format: texture_format, 92 | color_blend: wgpu::BlendDescriptor::REPLACE, 93 | alpha_blend: wgpu::BlendDescriptor::REPLACE, 94 | write_mask: wgpu::ColorWrite::ALL, 95 | }], 96 | primitive_topology: wgpu::PrimitiveTopology::PointList, 97 | depth_stencil_state: None, 98 | vertex_state: wgpu::VertexStateDescriptor { 99 | index_format: wgpu::IndexFormat::Uint16, 100 | vertex_buffers: &[Point::desc(&Point::attributes())], 101 | }, 102 | sample_count: 1, 103 | sample_mask: !0, 104 | alpha_to_coverage_enabled: false, 105 | }) 106 | } 107 | -------------------------------------------------------------------------------- /src/window/lines/builder.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | 3 | use super::line::Line; 4 | use super::renderer::LinesRendener; 5 | use super::source::LineSource; 6 | use crate::pipes::{GpuVec, StaticShaderModule, VertexFormat}; 7 | 8 | use nalgebra::Point3; 9 | use slam_cv::Number; 10 | 11 | pub struct LinesBuilder 12 | where 13 | N: 'static + Number, 14 | Point3: VertexFormat, 15 | S: LineSource, 16 | { 17 | pub source: S, 18 | 19 | number: PhantomData, 20 | } 21 | 22 | impl LinesBuilder 23 | where 24 | N: 'static + Number, 25 | Point3: VertexFormat, 26 | S: LineSource, 27 | { 28 | pub fn new(source: S) -> Self { 29 | Self { 30 | source, 31 | 32 | number: Default::default(), 33 | } 34 | } 35 | 36 | pub fn build( 37 | self, 38 | device: &wgpu::Device, 39 | texture_format: wgpu::TextureFormat, 40 | uniform_bind_group_layout: &wgpu::BindGroupLayout, 41 | ) -> LinesRendener { 42 | let render_pipeline = 43 | build_render_pipeline(device, texture_format, uniform_bind_group_layout); 44 | 45 | LinesRendener { 46 | render_pipeline, 47 | buffer: GpuVec::new(wgpu::BufferUsage::VERTEX), 48 | 49 | number: Default::default(), 50 | source: self.source, 51 | } 52 | } 53 | } 54 | 55 | pub fn build_render_pipeline( 56 | device: &wgpu::Device, 57 | texture_format: wgpu::TextureFormat, 58 | uniform_bind_group_layout: &wgpu::BindGroupLayout, 59 | ) -> wgpu::RenderPipeline { 60 | const VS_SRC: StaticShaderModule = StaticShaderModule { 61 | spirv_source: include_bytes!("../points/shader.vert.spv"), 62 | entry_point: None, 63 | }; 64 | const FS_SRC: StaticShaderModule = StaticShaderModule { 65 | spirv_source: include_bytes!("../points/shader.frag.spv"), 66 | entry_point: None, 67 | }; 68 | 69 | let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { 70 | bind_group_layouts: &[&uniform_bind_group_layout], 71 | }); 72 | 73 | device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { 74 | layout: &render_pipeline_layout, 75 | vertex_stage: wgpu::ProgrammableStageDescriptor { 76 | module: &VS_SRC.build(device), 77 | entry_point: VS_SRC.entry_point(), 78 | }, 79 | fragment_stage: Some(wgpu::ProgrammableStageDescriptor { 80 | module: &FS_SRC.build(device), 81 | entry_point: FS_SRC.entry_point(), 82 | }), 83 | rasterization_state: Some(wgpu::RasterizationStateDescriptor { 84 | front_face: wgpu::FrontFace::Ccw, 85 | cull_mode: wgpu::CullMode::Back, 86 | depth_bias: 0, 87 | depth_bias_slope_scale: 0.0, 88 | depth_bias_clamp: 0.0, 89 | }), 90 | color_states: &[wgpu::ColorStateDescriptor { 91 | format: texture_format, 92 | color_blend: wgpu::BlendDescriptor::REPLACE, 93 | alpha_blend: wgpu::BlendDescriptor::REPLACE, 94 | write_mask: wgpu::ColorWrite::ALL, 95 | }], 96 | primitive_topology: wgpu::PrimitiveTopology::LineList, 97 | depth_stencil_state: None, 98 | vertex_state: wgpu::VertexStateDescriptor { 99 | index_format: wgpu::IndexFormat::Uint16, 100 | vertex_buffers: &[Line::desc(&Line::attributes())], 101 | }, 102 | sample_count: 1, 103 | sample_mask: !0, 104 | alpha_to_coverage_enabled: false, 105 | }) 106 | } 107 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::{Isometry3, Point2, Point3, Quaternion, Translation3, Unit}; 2 | use rand_distr::{Distribution, StandardNormal}; 3 | 4 | #[derive(Clone)] 5 | struct MyWorld { 6 | points: Vec, 7 | frames: Vec, 8 | } 9 | 10 | #[derive(Clone)] 11 | struct MyKeyFrame(Isometry3); 12 | 13 | #[derive(Clone)] 14 | struct MyFeature(Point3); 15 | 16 | impl slam_cv::vo::World for MyWorld { 17 | type Number = f32; 18 | type KeyFrame = MyKeyFrame; 19 | type Landmark = MyFeature; 20 | 21 | fn for_landmarks(&self, mut f: F) 22 | where 23 | F: FnMut(&Self::Landmark), 24 | { 25 | for feature in &self.points { 26 | f(feature); 27 | } 28 | } 29 | 30 | fn collect_landmarks(&self, f: F) -> Vec 31 | where 32 | F: FnMut(&Self::Landmark) -> B, 33 | { 34 | self.points.iter().map(f).collect() 35 | } 36 | 37 | fn collect_keyframes(&self, f: F) -> Vec 38 | where 39 | F: FnMut(&Self::KeyFrame) -> B, 40 | { 41 | self.frames.iter().map(f).collect() 42 | } 43 | 44 | fn load(&self) {} 45 | 46 | fn save(&self) {} 47 | } 48 | 49 | impl slam_cv::frame::KeyFrame for MyKeyFrame { 50 | type Number = f32; 51 | type Feature = MyFeature; 52 | 53 | fn for_landmarks(&self, _f: F) 54 | where 55 | F: FnMut(&Self::Feature), 56 | { 57 | unimplemented!() 58 | } 59 | 60 | fn isometry(&self) -> Isometry3 { 61 | self.0 62 | } 63 | } 64 | 65 | impl slam_cv::feature::Feature for MyFeature { 66 | type Number = f32; 67 | } 68 | 69 | impl slam_cv::feature::KeyPoint for MyFeature { 70 | fn point_image(&self) -> Point2 { 71 | self.0.xy() 72 | } 73 | } 74 | 75 | impl slam_cv::feature::Descriptor for MyFeature { 76 | type Distance = (); 77 | 78 | fn get_distance(&self, _other: &Self) -> Self::Distance { 79 | unimplemented!() 80 | } 81 | } 82 | 83 | impl slam_cv::feature::Landmark for MyFeature { 84 | fn point_world(&self) -> Point3 { 85 | self.0 86 | } 87 | } 88 | 89 | fn main() { 90 | const NUM_POINTS: usize = 10_000; 91 | const NUM_FRAMES: usize = 20; 92 | 93 | let mut rng = rand::thread_rng(); 94 | let mut rng = StandardNormal.sample_iter(&mut rng); 95 | 96 | let mut tx = 0.0; 97 | let mut ty = 0.0; 98 | let mut tz = 0.0; 99 | 100 | let mut rx = 0.0; 101 | let mut ry = 0.0; 102 | let mut rz = 0.0; 103 | let mut rw = std::f32::consts::FRAC_PI_2; 104 | 105 | let world = MyWorld { 106 | points: (0..NUM_POINTS) 107 | .map(|_| { 108 | let x = rng.next().unwrap(); 109 | let y = rng.next().unwrap(); 110 | let z = rng.next().unwrap(); 111 | MyFeature(Point3::new(x, y, z - 10.0)) 112 | }) 113 | .collect(), 114 | frames: (0..NUM_FRAMES) 115 | .map(|_| { 116 | tx += rng.next().unwrap() * 0.1; 117 | ty += rng.next().unwrap() * 0.1; 118 | tz -= 0.5; 119 | let translation = Translation3::new(tx, ty, tz); 120 | 121 | rx += rng.next().unwrap() * 0.1; 122 | ry += rng.next().unwrap() * 0.1; 123 | rz += rng.next().unwrap() * 0.1; 124 | rw += rng.next().unwrap() * 0.1; 125 | let rotation = Unit::new_normalize(Quaternion::new(rx, ry, rz, rw)); 126 | 127 | MyKeyFrame(Isometry3::from_parts(translation, rotation)) 128 | }) 129 | .collect(), 130 | }; 131 | 132 | #[cfg(target_arch = "wasm32")] 133 | { 134 | std::panic::set_hook(Box::new(console_error_panic_hook::hook)); 135 | console_log::init().expect("could not initialize logger"); 136 | } 137 | 138 | // make a window with this thread 139 | slam_viewer::alloc_thread().add_world(world).run(); 140 | 141 | // make a window with a new thread 142 | // slam_viewer::alloc_thread().add_world(world).spawn().wait(); 143 | } 144 | -------------------------------------------------------------------------------- /src/window/isometries/isometry.rs: -------------------------------------------------------------------------------- 1 | use super::super::lines::Line; 2 | use super::super::points::Point; 3 | use crate::pipes::{GpuVertex, VertexFormat}; 4 | 5 | use nalgebra::{Isometry3, Matrix4, Point2, Point3, Vector4}; 6 | use slam_cv::Number; 7 | 8 | #[repr(C)] 9 | #[derive(Copy, Clone, Debug, Default)] 10 | pub struct Isometry 11 | where 12 | N: 'static + Number, 13 | Point3: VertexFormat, 14 | { 15 | lines: [Line; 6], 16 | } 17 | 18 | impl Isometry 19 | where 20 | N: 'static + Number, 21 | Point3: VertexFormat, 22 | { 23 | pub fn from_iso(iso: Isometry3, camera_size: Point2, color: Point3) -> Self { 24 | Self::from_homogeneous(iso.to_homogeneous(), camera_size, color) 25 | } 26 | 27 | pub fn from_homogeneous(iso: Matrix4, camera_size: Point2, color: Point3) -> Self { 28 | let size = Point2::new(camera_size.x, camera_size.y); 29 | let pose = iso; 30 | 31 | Self { 32 | lines: [ 33 | Line { 34 | start: Point { 35 | position: map_pose(&pose, -size.x, -size.y), 36 | color, 37 | }, 38 | end: Point { 39 | position: map_pose(&pose, size.x, size.y), 40 | color, 41 | }, 42 | }, 43 | Line { 44 | start: Point { 45 | position: map_pose(&pose, -size.x, size.y), 46 | color, 47 | }, 48 | end: Point { 49 | position: map_pose(&pose, size.x, -size.y), 50 | color, 51 | }, 52 | }, 53 | Line { 54 | start: Point { 55 | position: map_pose(&pose, size.x, size.y), 56 | color, 57 | }, 58 | end: Point { 59 | position: map_pose(&pose, size.x, -size.y), 60 | color, 61 | }, 62 | }, 63 | Line { 64 | start: Point { 65 | position: map_pose(&pose, -size.x, size.y), 66 | color, 67 | }, 68 | end: Point { 69 | position: map_pose(&pose, -size.x, -size.y), 70 | color, 71 | }, 72 | }, 73 | Line { 74 | start: Point { 75 | position: map_pose(&pose, size.x, size.y), 76 | color, 77 | }, 78 | end: Point { 79 | position: map_pose(&pose, -size.x, size.y), 80 | color, 81 | }, 82 | }, 83 | Line { 84 | start: Point { 85 | position: map_pose(&pose, size.x, -size.y), 86 | color, 87 | }, 88 | end: Point { 89 | position: map_pose(&pose, -size.x, -size.y), 90 | color, 91 | }, 92 | }, 93 | ], 94 | } 95 | } 96 | } 97 | 98 | fn map_pose(pose: &Matrix4, x: N, y: N) -> Point3 99 | where 100 | N: 'static + Number, 101 | Point3: VertexFormat, 102 | { 103 | let m = pose * Vector4::new(x, y, N::zero(), N::one()); 104 | Point3::new(m.x, m.y, m.z) 105 | } 106 | 107 | impl GpuVertex for Isometry 108 | where 109 | N: 'static + Number, 110 | Point3: VertexFormat, 111 | { 112 | fn weight() -> u64 { 113 | 12 114 | } 115 | } 116 | 117 | unsafe impl bytemuck::Pod for Isometry 118 | where 119 | N: 'static + Number, 120 | Point3: VertexFormat, 121 | { 122 | } 123 | unsafe impl bytemuck::Zeroable for Isometry 124 | where 125 | N: 'static + Number, 126 | Point3: VertexFormat, 127 | { 128 | } 129 | -------------------------------------------------------------------------------- /src/engine/builder.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use super::base::Engine; 4 | use super::timer::Timer; 5 | use crate::pipes::{PipelineBuilder, VertexFormat}; 6 | use crate::window::{WindowBuilder, WindowEventState}; 7 | 8 | #[cfg(not(target_arch = "wasm32"))] 9 | use futures::executor::block_on; 10 | 11 | #[cfg(target_arch = "wasm32")] 12 | use wasm_bindgen_futures::spawn_local as block_on; 13 | 14 | use nalgebra::Point3; 15 | use slam_cv::Number; 16 | use winit::{ 17 | event::*, 18 | event_loop::{ControlFlow, EventLoop}, 19 | }; 20 | 21 | pub struct EngineBuilder 22 | where 23 | N: 'static + Number, 24 | Point3: VertexFormat, 25 | { 26 | pub windows: Vec<(WindowBuilder, Box>)>, 27 | } 28 | 29 | impl EngineBuilder 30 | where 31 | N: 'static + Number, 32 | Point3: VertexFormat, 33 | { 34 | pub fn run(self) { 35 | let event_loop = EventLoop::new(); 36 | block_on(self.run_forever(event_loop)) 37 | } 38 | 39 | pub fn spawn(self) -> Engine { 40 | Engine::new(move || { 41 | let event_loop = super::event_loop::new_event_loop(); 42 | block_on(self.run_forever(event_loop)) 43 | }) 44 | } 45 | 46 | async fn run_forever(self, event_loop: EventLoop<()>) { 47 | let mut windows = HashMap::new(); 48 | for (builder, pipe) in self.windows { 49 | let (id, window) = builder.build(&event_loop, pipe).await; 50 | windows.insert(id, window); 51 | } 52 | 53 | let mut timer = windows 54 | .values() 55 | .filter_map(|w| w.framerate) 56 | .min() 57 | .map(|f| { 58 | let micros = (1_000_000.0 / f as f64) as u64 - 500; 59 | std::time::Duration::from_micros(micros) 60 | }) 61 | .and_then(Timer::try_new); 62 | 63 | event_loop.run(move |event, _, control_flow| { 64 | match event { 65 | Event::WindowEvent { 66 | ref event, 67 | window_id, 68 | } => { 69 | if let Some(window) = windows.get_mut(&window_id) { 70 | if window.input(event) == WindowEventState::Unused { 71 | match event { 72 | WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, 73 | WindowEvent::KeyboardInput { input, .. } => { 74 | if let KeyboardInput { 75 | state: ElementState::Pressed, 76 | virtual_keycode: Some(VirtualKeyCode::Escape), 77 | .. 78 | } = input 79 | { 80 | *control_flow = ControlFlow::Exit 81 | } 82 | } 83 | WindowEvent::Resized(physical_size) => { 84 | window.resize(*physical_size); 85 | } 86 | WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { 87 | // new_inner_size is &mut so w have to dereference it twice 88 | window.resize(**new_inner_size); 89 | } 90 | _ => {} 91 | } 92 | } 93 | } 94 | } 95 | Event::RedrawRequested(window_id) => { 96 | if let Some(window) = windows.get_mut(&window_id) { 97 | window.update(); 98 | window.render(); 99 | } 100 | } 101 | Event::MainEventsCleared => { 102 | if let Some(timer) = &mut timer { 103 | timer.sync(); 104 | } 105 | 106 | for engine_window in windows.values() { 107 | engine_window.request_redraw(); 108 | } 109 | } 110 | _ => {} 111 | } 112 | }) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/window/models/matches.rs: -------------------------------------------------------------------------------- 1 | use super::super::builder::WindowBuilder; 2 | use super::super::camera::{CameraControllerConfig, CameraFrustum}; 3 | use super::super::lines::{LineSource, LinesBuilder, LinesRendener}; 4 | use super::super::points::{PointSource, PointsBuilder, PointsRendener}; 5 | use crate::pipes::{PipelineBuilder, PipelineDataBuilder, PipelineRenderer, VertexFormat}; 6 | 7 | use cv_core::FeatureMatch; 8 | use nalgebra::{base::allocator::Allocator, DefaultAllocator, DimName, Point, Point3, U2, U3}; 9 | use slam_cv::Number; 10 | 11 | #[derive(Clone)] 12 | pub struct MatchesModel 13 | where 14 | N: 'static + Number, 15 | D: DimName, 16 | DefaultAllocator: Allocator, 17 | { 18 | matches: Vec>>, 19 | } 20 | 21 | impl MatchesModel 22 | where 23 | N: 'static + Number, 24 | D: DimName, 25 | DefaultAllocator: Allocator, 26 | { 27 | pub fn new(matches: Vec>>) -> Self { 28 | Self { matches } 29 | } 30 | } 31 | 32 | impl PipelineDataBuilder for MatchesModel 33 | where 34 | Self: PointSource + LineSource + Send, 35 | D: DimName, 36 | DefaultAllocator: Allocator, 37 | { 38 | type Builder = Self; 39 | 40 | fn default_window(&self) -> WindowBuilder { 41 | WindowBuilder { 42 | title: Some("Matches Viewer".to_string()), 43 | framerate: Some(120), 44 | 45 | camera: CameraFrustum { 46 | eye: Point3::new(0., 2., 5.), 47 | at: Point3::new(0., 0., 0.), 48 | 49 | fovy: std::f32::consts::FRAC_PI_4, 50 | znear: 0.1, 51 | zfar: 100.0, 52 | }, 53 | camera_controller: CameraControllerConfig::default(), 54 | } 55 | } 56 | 57 | fn build_data(self) -> Self::Builder { 58 | self 59 | } 60 | } 61 | 62 | impl PipelineBuilder for MatchesModel 63 | where 64 | N: 'static + Number, 65 | Point3: VertexFormat, 66 | D: DimName, 67 | DefaultAllocator: Allocator, 68 | MatchesModel: PipelineDataBuilder + PointSource + LineSource, 69 | { 70 | fn build( 71 | self: Box, 72 | device: &wgpu::Device, 73 | texture_format: wgpu::TextureFormat, 74 | uniform_bind_group_layout: &wgpu::BindGroupLayout, 75 | ) -> Box { 76 | let matches = *self; 77 | Box::new(MatchesRenderer { 78 | points_1: PointsBuilder::new(matches.clone()).build( 79 | device, 80 | texture_format, 81 | uniform_bind_group_layout, 82 | ), 83 | points_2: PointsBuilder::new(matches.clone()).build( 84 | device, 85 | texture_format, 86 | uniform_bind_group_layout, 87 | ), 88 | lines: LinesBuilder::new(matches).build( 89 | device, 90 | texture_format, 91 | uniform_bind_group_layout, 92 | ), 93 | }) 94 | } 95 | } 96 | 97 | pub struct MatchesRenderer 98 | where 99 | N: 'static + Number, 100 | Point3: VertexFormat, 101 | D: DimName, 102 | DefaultAllocator: Allocator, 103 | MatchesModel: PointSource + LineSource, 104 | { 105 | points_1: PointsRendener>, 106 | points_2: PointsRendener>, 107 | lines: LinesRendener>, 108 | } 109 | 110 | impl PipelineRenderer for MatchesRenderer 111 | where 112 | N: 'static + Number, 113 | Point3: VertexFormat, 114 | D: DimName, 115 | DefaultAllocator: Allocator, 116 | MatchesModel: PointSource + LineSource, 117 | { 118 | fn render<'a>(&'a mut self, device: &wgpu::Device, render_pass: &mut wgpu::RenderPass<'a>) { 119 | self.points_1.render(device, render_pass); 120 | self.points_2.render(device, render_pass); 121 | self.lines.render(device, render_pass); 122 | } 123 | } 124 | 125 | impl PointSource for MatchesModel 126 | where 127 | N: 'static + Number, 128 | Point3: VertexFormat, 129 | { 130 | fn collect_visual_points(&self) -> Vec> { 131 | self.matches 132 | .iter() 133 | .map(|&m| { 134 | let p1 = Point3::new(m.0.x, m.0.y, N::zero()); 135 | let p2 = Point3::new(m.1.x, m.1.y, N::zero()); 136 | vec![p1, p2] 137 | }) 138 | .flatten() 139 | .collect() 140 | } 141 | } 142 | 143 | impl PointSource for MatchesModel 144 | where 145 | N: 'static + Number, 146 | Point3: VertexFormat, 147 | { 148 | fn collect_visual_points(&self) -> Vec> { 149 | self.matches 150 | .iter() 151 | .map(|&m| vec![m.0, m.1]) 152 | .flatten() 153 | .collect() 154 | } 155 | } 156 | 157 | impl LineSource for MatchesModel 158 | where 159 | N: 'static + Number, 160 | Point3: VertexFormat, 161 | { 162 | fn collect_visual_lines(&self) -> Vec<[Point3; 2]> { 163 | self.matches 164 | .iter() 165 | .map(|&m| { 166 | let p1 = Point3::new(m.0.x, m.0.y, N::zero()); 167 | let p2 = Point3::new(m.1.x, m.1.y, N::zero()); 168 | [p1, p2] 169 | }) 170 | .collect() 171 | } 172 | } 173 | 174 | impl LineSource for MatchesModel 175 | where 176 | N: 'static + Number, 177 | Point3: VertexFormat, 178 | { 179 | fn collect_visual_lines(&self) -> Vec<[Point3; 2]> { 180 | self.matches.iter().map(|&m| [m.0, m.1]).collect() 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/window/models/world.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | 3 | use super::super::builder::WindowBuilder; 4 | use super::super::camera::{CameraControllerConfig, CameraFrustum}; 5 | use super::super::isometries::{IsometriesBuilder, IsometriesRendener, IsometrySource}; 6 | use super::super::lines::{LineSource, LinesBuilder, LinesRendener}; 7 | use super::super::points::{PointSource, PointsBuilder, PointsRendener}; 8 | use crate::pipes::{PipelineBuilder, PipelineDataBuilder, PipelineRenderer, VertexFormat}; 9 | 10 | use nalgebra::{Isometry3, Point3}; 11 | use slam_cv::{feature::Landmark, frame::KeyFrame, vo::World, Number}; 12 | 13 | #[derive(Clone)] 14 | pub struct WorldModel 15 | where 16 | N: 'static + Number, 17 | Point3: VertexFormat, 18 | F: 'static + Landmark + Clone, 19 | KF: 'static + KeyFrame + Clone, 20 | W: 'static + World + Clone, 21 | { 22 | world: W, 23 | 24 | _feature: PhantomData, 25 | _keyframees: PhantomData, 26 | } 27 | 28 | impl WorldModel 29 | where 30 | N: 'static + Number, 31 | Point3: VertexFormat, 32 | F: 'static + Landmark + Clone, 33 | KF: 'static + KeyFrame + Clone, 34 | W: 'static + World + Clone, 35 | { 36 | pub fn new(world: W) -> Self { 37 | Self { 38 | world, 39 | 40 | _feature: Default::default(), 41 | _keyframees: Default::default(), 42 | } 43 | } 44 | } 45 | 46 | impl PipelineDataBuilder for WorldModel 47 | where 48 | Self: Send, 49 | F: 'static + Landmark + Clone, 50 | KF: 'static + KeyFrame + Clone, 51 | W: 'static + World + Clone, 52 | { 53 | type Builder = Self; 54 | 55 | fn default_window(&self) -> WindowBuilder { 56 | WindowBuilder { 57 | title: Some("Map Viewer".to_string()), 58 | framerate: Some(120), 59 | 60 | camera: CameraFrustum { 61 | eye: Point3::new(0., 2., 5.), 62 | at: Point3::new(0., 0., 0.), 63 | 64 | fovy: std::f32::consts::FRAC_PI_4, 65 | znear: 0.1, 66 | zfar: 100.0, 67 | }, 68 | camera_controller: CameraControllerConfig::default(), 69 | } 70 | } 71 | 72 | fn build_data(self) -> Self::Builder { 73 | self 74 | } 75 | } 76 | 77 | impl PipelineBuilder for WorldModel 78 | where 79 | N: 'static + Number, 80 | Point3: VertexFormat, 81 | F: 'static + Landmark + Clone, 82 | KF: 'static + KeyFrame + Clone, 83 | W: 'static + World + Clone, 84 | WorldModel: 85 | PipelineDataBuilder + PointSource + LineSource + IsometrySource, 86 | { 87 | fn build( 88 | self: Box, 89 | device: &wgpu::Device, 90 | texture_format: wgpu::TextureFormat, 91 | uniform_bind_group_layout: &wgpu::BindGroupLayout, 92 | ) -> Box { 93 | let world = *self; 94 | Box::new(WorldRenderer { 95 | points: PointsBuilder::new(world.clone()).build( 96 | device, 97 | texture_format, 98 | uniform_bind_group_layout, 99 | ), 100 | lines: LinesBuilder::new(world.clone()).build( 101 | device, 102 | texture_format, 103 | uniform_bind_group_layout, 104 | ), 105 | isometries: IsometriesBuilder::new(world).build( 106 | device, 107 | texture_format, 108 | uniform_bind_group_layout, 109 | ), 110 | }) 111 | } 112 | } 113 | 114 | pub struct WorldRenderer 115 | where 116 | N: 'static + Number, 117 | Point3: VertexFormat, 118 | F: 'static + Landmark + Clone, 119 | KF: 'static + KeyFrame + Clone, 120 | W: 'static + World + Clone, 121 | WorldModel: PointSource + LineSource + IsometrySource, 122 | { 123 | points: PointsRendener>, 124 | lines: LinesRendener>, 125 | isometries: IsometriesRendener>, 126 | } 127 | 128 | impl PipelineRenderer for WorldRenderer 129 | where 130 | N: 'static + Number, 131 | Point3: VertexFormat, 132 | F: 'static + Landmark + Clone, 133 | KF: 'static + KeyFrame + Clone, 134 | W: 'static + World + Clone, 135 | WorldModel: PointSource + LineSource + IsometrySource, 136 | { 137 | fn render<'a>(&'a mut self, device: &wgpu::Device, render_pass: &mut wgpu::RenderPass<'a>) { 138 | self.points.render(device, render_pass); 139 | self.lines.render(device, render_pass); 140 | self.isometries.render(device, render_pass); 141 | } 142 | } 143 | 144 | impl PointSource for WorldModel 145 | where 146 | N: 'static + Number, 147 | Point3: VertexFormat, 148 | F: 'static + Landmark + Clone, 149 | KF: 'static + KeyFrame + Clone, 150 | W: 'static + World + Clone, 151 | { 152 | fn collect_visual_points(&self) -> Vec> { 153 | self.world.collect_landmarks(Landmark::point_world) 154 | } 155 | } 156 | 157 | impl LineSource for WorldModel 158 | where 159 | N: 'static + Number, 160 | Point3: VertexFormat, 161 | F: 'static + Landmark + Clone, 162 | KF: 'static + KeyFrame + Clone, 163 | W: 'static + World + Clone, 164 | { 165 | fn collect_visual_lines(&self) -> Vec<[Point3; 2]> { 166 | let mut prev = None; 167 | 168 | self.world.collect_keyframes(|kf| { 169 | let p = kf.isometry().translation.vector.into(); 170 | let line = match prev { 171 | Some(prev) => [prev, p], 172 | None => [p, p], 173 | }; 174 | prev = Some(p); 175 | line 176 | }) 177 | } 178 | } 179 | 180 | impl IsometrySource for WorldModel 181 | where 182 | F: 'static + Landmark + Clone, 183 | KF: 'static + KeyFrame + Clone, 184 | W: 'static + World + Clone, 185 | { 186 | const SIZE: [f32; 2] = [0.2, 0.16]; 187 | 188 | fn collect_visual_isometries(&self) -> Vec> { 189 | self.world.collect_keyframes(KF::isometry) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/window/camera/controller.rs: -------------------------------------------------------------------------------- 1 | //! Many of this code is from [kiss3d](https://github.com/sebcrozet/kiss3d) 2 | //! https://github.com/sebcrozet/kiss3d/blob/master/src/camera/first_person.rs 3 | 4 | use super::super::event::WindowEventState; 5 | use super::base::Camera; 6 | 7 | use nalgebra::Vector2; 8 | use slam_cv::Number; 9 | use winit::event::*; 10 | 11 | pub struct CameraControllerConfig 12 | where 13 | N: Number, 14 | { 15 | pub mouse_left_speed: N, 16 | pub mouse_right_speed: N, 17 | pub scroll_speed: N, 18 | pub keyboard_speed: N, 19 | } 20 | 21 | impl Default for CameraControllerConfig { 22 | fn default() -> Self { 23 | Self { 24 | mouse_left_speed: 1.0, 25 | mouse_right_speed: 5.0, 26 | scroll_speed: 1.0, 27 | keyboard_speed: 0.1, 28 | } 29 | } 30 | } 31 | 32 | impl Into> for CameraControllerConfig 33 | where 34 | N: Number, 35 | { 36 | fn into(self) -> CameraController { 37 | CameraController { 38 | config: self, 39 | 40 | window_size: Vector2::zeros(), 41 | 42 | cursor_d: Vector2::zeros(), 43 | cursor_pos: None, 44 | mouse_wheel_d: N::zero(), 45 | 46 | is_left_mouse_pressed: false, 47 | is_right_mouse_pressed: false, 48 | 49 | is_left_key_pressed: false, 50 | is_right_key_pressed: false, 51 | is_up_key_pressed: false, 52 | is_down_key_pressed: false, 53 | } 54 | } 55 | } 56 | 57 | pub struct CameraController 58 | where 59 | N: Number, 60 | { 61 | config: CameraControllerConfig, 62 | 63 | cursor_d: Vector2, 64 | cursor_pos: Option>, 65 | mouse_wheel_d: N, 66 | 67 | pub(crate) window_size: Vector2, 68 | 69 | is_left_mouse_pressed: bool, 70 | is_right_mouse_pressed: bool, 71 | 72 | is_left_key_pressed: bool, 73 | is_right_key_pressed: bool, 74 | is_up_key_pressed: bool, 75 | is_down_key_pressed: bool, 76 | } 77 | 78 | impl CameraController 79 | where 80 | N: Number, 81 | { 82 | pub fn process_events(&mut self, event: &WindowEvent) -> WindowEventState { 83 | match event { 84 | WindowEvent::Resized(size) => { 85 | self.window_size.x = N::from(size.width).unwrap(); 86 | self.window_size.y = N::from(size.height).unwrap(); 87 | WindowEventState::Unused 88 | } 89 | WindowEvent::KeyboardInput { 90 | input: 91 | KeyboardInput { 92 | state, 93 | virtual_keycode: Some(keycode), 94 | .. 95 | }, 96 | .. 97 | } => { 98 | let is_pressed = *state == ElementState::Pressed; 99 | match keycode { 100 | VirtualKeyCode::A | VirtualKeyCode::Left => { 101 | self.is_left_key_pressed = is_pressed; 102 | WindowEventState::Consumed 103 | } 104 | VirtualKeyCode::D | VirtualKeyCode::Right => { 105 | self.is_right_key_pressed = is_pressed; 106 | WindowEventState::Consumed 107 | } 108 | VirtualKeyCode::W | VirtualKeyCode::Up => { 109 | self.is_up_key_pressed = is_pressed; 110 | WindowEventState::Consumed 111 | } 112 | VirtualKeyCode::S | VirtualKeyCode::Down => { 113 | self.is_down_key_pressed = is_pressed; 114 | WindowEventState::Consumed 115 | } 116 | _ => WindowEventState::Unused, 117 | } 118 | } 119 | WindowEvent::MouseInput { state, button, .. } => { 120 | self.cursor_pos = None; 121 | 122 | let is_pressed = *state == ElementState::Pressed; 123 | match button { 124 | MouseButton::Left => { 125 | self.is_left_mouse_pressed = is_pressed; 126 | WindowEventState::Consumed 127 | } 128 | MouseButton::Right => { 129 | self.is_right_mouse_pressed = is_pressed; 130 | WindowEventState::Consumed 131 | } 132 | _ => WindowEventState::Unused, 133 | } 134 | } 135 | // TODO: calibrate 136 | WindowEvent::CursorMoved { position, .. } => { 137 | if self.is_left_mouse_pressed || self.is_right_mouse_pressed { 138 | let position = 139 | Vector2::new(N::from(position.x).unwrap(), N::from(position.y).unwrap()); 140 | 141 | if let Some(cursor_pos) = self.cursor_pos { 142 | let delta = position - cursor_pos; 143 | self.cursor_d += delta; 144 | } 145 | 146 | self.cursor_pos = Some(position); 147 | WindowEventState::Consumed 148 | } else { 149 | WindowEventState::Unused 150 | } 151 | } 152 | WindowEvent::MouseWheel { delta, .. } => { 153 | self.mouse_wheel_d += match delta { 154 | MouseScrollDelta::LineDelta(_, ny) => N::from(*ny).unwrap(), 155 | // TODO: calibrate 156 | MouseScrollDelta::PixelDelta(dp) => N::from(dp.x.max(dp.y)).unwrap(), 157 | }; 158 | WindowEventState::Consumed 159 | } 160 | _ => WindowEventState::Unused, 161 | } 162 | } 163 | 164 | pub fn update_camera(&mut self, camera: &mut Camera) { 165 | self.cursor_d.x /= self.window_size.x; 166 | self.cursor_d.y /= self.window_size.y; 167 | 168 | // mouse movement 169 | if self.is_left_mouse_pressed { 170 | camera.rotate(self.cursor_d * self.config.mouse_left_speed); 171 | } 172 | if self.is_right_mouse_pressed { 173 | camera.move_to(self.cursor_d * self.config.mouse_right_speed); 174 | } 175 | self.cursor_d = Vector2::zeros(); 176 | 177 | // scroll movement 178 | camera.scale(self.mouse_wheel_d * self.config.scroll_speed); 179 | self.mouse_wheel_d = N::zero(); 180 | 181 | // keyboard input 182 | let mut dkey = Vector2::::zeros(); 183 | if self.is_left_key_pressed { 184 | dkey.x += N::one(); 185 | } 186 | if self.is_right_key_pressed { 187 | dkey.x -= N::one(); 188 | } 189 | if self.is_up_key_pressed { 190 | dkey.y += N::one(); 191 | } 192 | if self.is_down_key_pressed { 193 | dkey.y -= N::one(); 194 | } 195 | dkey *= self.config.keyboard_speed; 196 | 197 | camera.move_to(Vector2::new(dkey.x, N::zero())); 198 | camera.scale(dkey.y); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/window/base.rs: -------------------------------------------------------------------------------- 1 | use super::builder::WindowBuilder; 2 | use super::camera::{Camera, CameraController}; 3 | use super::event::WindowEventState; 4 | use super::uniform::Uniforms; 5 | use crate::pipes::{PipelineBuilder, PipelineRenderer, VertexFormat}; 6 | 7 | use nalgebra::{Point3, Vector2}; 8 | use slam_cv::Number; 9 | use winit::{event::*, window}; 10 | 11 | pub struct Window 12 | where 13 | N: 'static + Number, 14 | Point3: VertexFormat, 15 | { 16 | window: window::Window, 17 | surface: wgpu::Surface, 18 | device: wgpu::Device, 19 | queue: wgpu::Queue, 20 | sc_desc: wgpu::SwapChainDescriptor, 21 | swap_chain: wgpu::SwapChain, 22 | 23 | pipeline_rendener: Box, 24 | 25 | // TODO move camera to ShaderPlugin 26 | camera: Camera, 27 | camera_controller: CameraController, 28 | 29 | pub framerate: Option, 30 | 31 | uniforms: Uniforms, 32 | uniform_buffer: wgpu::Buffer, 33 | uniform_bind_group: wgpu::BindGroup, 34 | } 35 | 36 | impl Window 37 | where 38 | N: 'static + Number, 39 | Point3: VertexFormat, 40 | { 41 | pub async fn new( 42 | window: window::Window, 43 | builder: WindowBuilder, 44 | pipeline_builder: Box>, 45 | ) -> Self { 46 | let size = window.inner_size(); 47 | 48 | #[cfg(target_arch = "wasm32")] 49 | { 50 | use winit::platform::web::WindowExtWebSys; 51 | 52 | // On wasm, append the canvas to the document body 53 | web_sys::window() 54 | .and_then(|win| win.document()) 55 | .and_then(|doc| doc.body()) 56 | .and_then(|body| { 57 | body.append_child(&web_sys::Element::from(window.canvas())) 58 | .ok() 59 | }) 60 | .expect("couldn't append canvas to document body"); 61 | } 62 | 63 | let instance = wgpu::Instance::new(); 64 | let surface = unsafe { instance.create_surface(&window) }; 65 | 66 | let adapter = instance 67 | .request_adapter( 68 | &wgpu::RequestAdapterOptions { 69 | power_preference: wgpu::PowerPreference::Default, 70 | compatible_surface: Some(&surface), 71 | }, 72 | // Vulkan + Metal + DX12 + Browser WebGPU 73 | wgpu::BackendBit::PRIMARY, 74 | ) 75 | .await 76 | .unwrap(); 77 | 78 | let (device, queue) = adapter 79 | .request_device( 80 | &wgpu::DeviceDescriptor { 81 | extensions: wgpu::Extensions { 82 | anisotropic_filtering: false, 83 | }, 84 | limits: Default::default(), 85 | }, 86 | None, 87 | ) 88 | .await 89 | .unwrap(); 90 | 91 | #[cfg(not(target_arch = "wasm32"))] 92 | let sc_format = wgpu::TextureFormat::Bgra8UnormSrgb; 93 | 94 | #[cfg(target_arch = "wasm32")] 95 | let sc_format = wgpu::TextureFormat::Bgra8Unorm; 96 | 97 | let sc_desc = wgpu::SwapChainDescriptor { 98 | usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, 99 | format: sc_format, 100 | width: size.width, 101 | height: size.height, 102 | present_mode: wgpu::PresentMode::Immediate, 103 | }; 104 | let swap_chain = device.create_swap_chain(&surface, &sc_desc); 105 | 106 | let camera = builder.camera.into(); 107 | let mut camera_controller: CameraController = builder.camera_controller.into(); 108 | 109 | camera_controller.window_size = 110 | Vector2::new(N::from(size.width).unwrap(), N::from(size.height).unwrap()); 111 | 112 | let mut uniforms = Uniforms::default(); 113 | uniforms.update_view_proj(&camera, Self::aspect(&sc_desc)); 114 | 115 | let uniform_buffer = device.create_buffer_with_data( 116 | bytemuck::cast_slice(&[uniforms]), 117 | wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, 118 | ); 119 | 120 | let uniform_bind_group_layout = 121 | device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { 122 | bindings: &[wgpu::BindGroupLayoutEntry { 123 | binding: 0, 124 | visibility: wgpu::ShaderStage::VERTEX, 125 | ty: wgpu::BindingType::UniformBuffer { dynamic: false }, 126 | }], 127 | label: Some("uniform_bind_group_layout"), 128 | }); 129 | 130 | let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { 131 | layout: &uniform_bind_group_layout, 132 | bindings: &[wgpu::Binding { 133 | binding: 0, 134 | resource: wgpu::BindingResource::Buffer { 135 | buffer: &uniform_buffer, 136 | // FYI: you can share a single buffer between bindings. 137 | range: 0..std::mem::size_of_val(&uniforms) as wgpu::BufferAddress, 138 | }, 139 | }], 140 | label: Some("uniform_bind_group"), 141 | }); 142 | 143 | let pipeline_rendener = 144 | pipeline_builder.build(&device, sc_desc.format, &uniform_bind_group_layout); 145 | 146 | let framerate = builder.framerate; 147 | 148 | Self { 149 | window, 150 | surface, 151 | device, 152 | queue, 153 | sc_desc, 154 | swap_chain, 155 | 156 | pipeline_rendener, 157 | 158 | camera, 159 | camera_controller, 160 | 161 | framerate, 162 | 163 | uniforms, 164 | uniform_buffer, 165 | uniform_bind_group, 166 | } 167 | } 168 | 169 | pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { 170 | self.sc_desc.width = new_size.width; 171 | self.sc_desc.height = new_size.height; 172 | self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc); 173 | } 174 | 175 | pub fn input(&mut self, event: &WindowEvent) -> WindowEventState { 176 | self.camera_controller.process_events(event) 177 | } 178 | 179 | pub fn update(&mut self) { 180 | self.camera_controller.update_camera(&mut self.camera); 181 | self.uniforms 182 | .update_view_proj(&self.camera, Self::aspect(&self.sc_desc)); 183 | 184 | let mut encoder = self 185 | .device 186 | .create_command_encoder(&wgpu::CommandEncoderDescriptor { 187 | label: Some("update encoder"), 188 | }); 189 | let staging_buffer = self.device.create_buffer_with_data( 190 | bytemuck::cast_slice(&[self.uniforms]), 191 | wgpu::BufferUsage::COPY_SRC, 192 | ); 193 | 194 | encoder.copy_buffer_to_buffer( 195 | &staging_buffer, 196 | 0, 197 | &self.uniform_buffer, 198 | 0, 199 | std::mem::size_of::>() as wgpu::BufferAddress, 200 | ); 201 | 202 | // We need to remember to submit our CommandEncoder's output 203 | // otherwise we won't see any change. 204 | self.queue.submit(Some(encoder.finish())); 205 | } 206 | 207 | pub fn render(&mut self) { 208 | let frame = self 209 | .swap_chain 210 | .get_next_texture() 211 | .expect("Timeout getting texture"); 212 | 213 | let mut encoder = self 214 | .device 215 | .create_command_encoder(&wgpu::CommandEncoderDescriptor { 216 | label: Some("Render Encoder"), 217 | }); 218 | 219 | { 220 | let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 221 | color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { 222 | attachment: &frame.view, 223 | resolve_target: None, 224 | load_op: wgpu::LoadOp::Clear, 225 | store_op: wgpu::StoreOp::Store, 226 | clear_color: wgpu::Color::BLACK, 227 | }], 228 | depth_stencil_attachment: None, 229 | }); 230 | 231 | render_pass.set_bind_group(0, &self.uniform_bind_group, &[]); 232 | self.pipeline_rendener 233 | .render(&self.device, &mut render_pass); 234 | } 235 | 236 | self.queue.submit(Some(encoder.finish())); 237 | } 238 | 239 | pub fn request_redraw(&self) { 240 | self.window.request_redraw(); 241 | } 242 | 243 | fn aspect(sc_desc: &wgpu::SwapChainDescriptor) -> N { 244 | N::from(sc_desc.width).unwrap() / N::from(sc_desc.height).unwrap() 245 | } 246 | } 247 | --------------------------------------------------------------------------------