Skip to main content

selection/
selection.rs

1//! This example enables viewport-based selsection of Gaussians using the `viewer-selection` feature.
2//!
3//! For example, to filter the selected Gaussians:
4//!
5//! ```sh
6//! cargo run --example selection --features="viewer-selection" -- --model path/to/model.ply --filter
7//! ```
8//!
9//! To view more options and the controls, run with `--help`:
10//!
11//! ```sh
12//! cargo run --example selection --features="viewer-selection" -- --help
13//! ```
14
15use std::sync::Arc;
16
17use clap::Parser;
18use glam::*;
19use winit::{error::EventLoopError, event_loop::EventLoop, keyboard::KeyCode, window::Window};
20
21use wgpu_3dgs_viewer::{
22    self as gs,
23    core::{BufferWrapper, GaussiansSource},
24    editor::{BasicColorRgbOverrideOrHsvModifiersPod, Modifier},
25};
26
27mod utils;
28use utils::core;
29
30/// The command line arguments.
31#[derive(Parser, Debug)]
32#[command(
33    version,
34    about,
35    long_about = "\
36    A 3D Gaussian splatting viewer written in Rust using wgpu.\n\
37    \n\
38    Use W, A, S, D, Space, Shift to move, use mouse to rotate.\n\
39    Use N to disable selection mode.\n\
40    Use B to toggle brush selection mode.\n\
41    Use R to toggle rectangle selection mode.\n\
42    Use I to invert selection, has immediate effect in filter mode.\n\
43    Use Left Click to use the current selector.\n\
44    "
45)]
46struct Args {
47    /// Path to the .ply file.
48    #[arg(short, long)]
49    model: String,
50
51    /// Enable filter mode, where instead of modifying the color, it filters the selected Gaussians.
52    #[arg(short, long)]
53    filter: bool,
54
55    /// Enable immediate mode, where the selection is applied while still selecting.
56    #[arg(short, long)]
57    immediate: bool,
58}
59
60fn main() -> Result<(), EventLoopError> {
61    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
62
63    let event_loop = EventLoop::new()?;
64    event_loop.run_app(&mut core::App::<System>::new(Args::parse()))?;
65    Ok(())
66}
67
68/// The application system.
69#[allow(dead_code)]
70struct System {
71    surface: wgpu::Surface<'static>,
72    queue: wgpu::Queue,
73    device: wgpu::Device,
74    config: wgpu::SurfaceConfiguration,
75
76    filter: bool,
77    immediate: bool,
78    inverted: bool,
79    selector_type: Option<gs::selection::ViewportSelectorType>,
80
81    camera: gs::Camera,
82    gaussians: gs::core::Gaussians,
83    viewer: gs::Viewer,
84    selector: gs::selection::ViewportSelector,
85
86    viewport_selection_modifier: gs::editor::NonDestructiveModifier<
87        gs::DefaultGaussianPod,
88        gs::editor::BasicSelectionModifier<gs::DefaultGaussianPod>,
89    >,
90    viewport_texture_overlay_renderer: utils::selection::ViewportTextureOverlayRenderer,
91}
92
93impl core::System for System {
94    type Args = Args;
95
96    async fn init(window: Arc<Window>, args: &Args) -> Self {
97        let model_path = &args.model;
98        let filter = args.filter;
99        let immediate = args.immediate;
100        let size = window.inner_size();
101
102        log::debug!("Creating wgpu instance");
103        let instance =
104            wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
105
106        log::debug!("Creating window surface");
107        let surface = instance.create_surface(window.clone()).expect("surface");
108
109        log::debug!("Requesting adapter");
110        let adapter = instance
111            .request_adapter(&wgpu::RequestAdapterOptions {
112                power_preference: wgpu::PowerPreference::HighPerformance,
113                compatible_surface: Some(&surface),
114                force_fallback_adapter: false,
115            })
116            .await
117            .expect("adapter");
118
119        log::debug!("Requesting device");
120        let (device, queue) = adapter
121            .request_device(&wgpu::DeviceDescriptor {
122                label: Some("Device"),
123                required_limits: adapter.limits(),
124                ..Default::default()
125            })
126            .await
127            .expect("device");
128
129        let surface_caps = surface.get_capabilities(&adapter);
130        let surface_format = surface_caps.formats[0];
131        let config = wgpu::SurfaceConfiguration {
132            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
133            format: surface_format,
134            width: size.width.max(1),
135            height: size.height.max(1),
136            present_mode: surface_caps.present_modes[0],
137            alpha_mode: surface_caps.alpha_modes[0],
138            view_formats: vec![surface_format.remove_srgb_suffix()],
139            desired_maximum_frame_latency: 2,
140        };
141
142        log::debug!("Configuring surface");
143        surface.configure(&device, &config);
144
145        log::debug!("Creating gaussians");
146        let gaussians = [GaussiansSource::Ply, GaussiansSource::Spz]
147            .into_iter()
148            .find_map(|source| gs::core::Gaussians::read_from_file(model_path, source).ok())
149            .expect("gaussians");
150
151        log::debug!("Creating camera");
152        let camera = gs::Camera::new(0.1..1e4, 60f32.to_radians());
153
154        log::debug!("Creating viewer");
155        let mut viewer = gs::Viewer::new_with_options(
156            &device,
157            config.view_formats[0],
158            &gaussians,
159            gs::ViewerCreateOptions {
160                gaussians_buffer_usage:
161                    gs::core::GaussiansBuffer::<gs::DefaultGaussianPod>::DEFAULT_USAGES
162                        | wgpu::BufferUsages::COPY_SRC,
163                ..Default::default()
164            },
165        )
166        .expect("viewer");
167        viewer.update_model_transform(
168            &queue,
169            Vec3::ZERO,
170            Quat::from_axis_angle(Vec3::Z, 180f32.to_radians()),
171            Vec3::ONE,
172        );
173
174        log::debug!("Creating selector");
175        let mut selector = gs::selection::ViewportSelector::new(
176            &device,
177            &queue,
178            UVec2::new(size.width, size.height),
179            &viewer.camera_buffer,
180        )
181        .expect("selector");
182        selector.selector_type = gs::selection::ViewportSelectorType::Brush;
183
184        log::debug!("Creating selection viewport selection modifier");
185        let mut viewport_selection_modifier = gs::editor::NonDestructiveModifier::new(
186            &device,
187            &queue,
188            gs::editor::BasicSelectionModifier::new_with_basic_modifier(
189                &device,
190                &viewer.gaussians_buffer,
191                &viewer.model_transform_buffer,
192                &viewer.gaussian_transform_buffer,
193                vec![gs::selection::create_viewport_bundle::<
194                    gs::DefaultGaussianPod,
195                >(&device)],
196            ),
197            &viewer.gaussians_buffer,
198        )
199        .expect("modifier");
200
201        let viewport_selection_bind_group = viewport_selection_modifier.modifier.selection.bundles
202            [0]
203        .create_bind_group(
204            &device,
205            // index 0 is the Gaussians buffer, so we use 1,
206            // see docs of create_viewport_bundle
207            1,
208            [
209                viewer.camera_buffer.buffer().as_entire_binding(),
210                wgpu::BindingResource::TextureView(selector.texture().view()),
211            ],
212        )
213        .expect("bind group");
214
215        viewport_selection_modifier.modifier.selection_expr =
216            gs::editor::SelectionExpr::Selection(0, vec![viewport_selection_bind_group]);
217
218        viewport_selection_modifier // Non destructive modifier
219            .modifier // Selection modifier
220            .modifier // Basic modifier
221            .basic_color_modifiers_buffer
222            .update_with_pod(
223                &queue,
224                &gs::editor::BasicColorModifiersPod {
225                    rgb_or_hsv: BasicColorRgbOverrideOrHsvModifiersPod::new_rgb_override(
226                        Vec3::new(1.0, 1.0, 0.0),
227                    ),
228                    ..Default::default()
229                },
230            );
231
232        log::debug!("Creating selection viewport texture overlay renderer");
233        let viewport_texture_overlay_renderer =
234            utils::selection::ViewportTextureOverlayRenderer::new(
235                &device,
236                config.view_formats[0],
237                selector.texture(),
238            );
239
240        log::info!("System initialized");
241
242        Self {
243            surface,
244            device,
245            queue,
246            config,
247
248            filter,
249            immediate,
250            inverted: filter,
251            selector_type: None,
252
253            camera,
254            gaussians,
255            viewer,
256            selector,
257
258            viewport_selection_modifier,
259            viewport_texture_overlay_renderer,
260        }
261    }
262
263    fn update(&mut self, input: &core::Input, delta_time: f32) {
264        // Toggle selection mode
265        if input.pressed_keys.contains(&KeyCode::KeyN) {
266            self.selector_type = None;
267            log::info!("Selector: None");
268        }
269        if input.pressed_keys.contains(&KeyCode::KeyR) {
270            self.selector_type = Some(gs::selection::ViewportSelectorType::Rectangle);
271            log::info!("Selector: Rectangle");
272            self.selector.selector_type = gs::selection::ViewportSelectorType::Rectangle;
273        }
274        if input.pressed_keys.contains(&KeyCode::KeyB) {
275            self.selector_type = Some(gs::selection::ViewportSelectorType::Brush);
276            log::info!("Selector: Brush");
277            self.selector.selector_type = gs::selection::ViewportSelectorType::Brush;
278        }
279        if input.pressed_keys.contains(&KeyCode::KeyI) {
280            self.inverted = !self.inverted;
281            log::info!("Inverted: {}", self.inverted);
282            if self.filter {
283                self.viewer
284                    .invert_selection_buffer
285                    .update(&self.queue, self.inverted);
286            }
287        }
288
289        if self.selector_type.is_some() {
290            self.update_selection(input, delta_time);
291        } else {
292            self.update_movement(input, delta_time);
293        }
294
295        // Update the viewer
296        self.viewer.update_camera(
297            &self.queue,
298            &self.camera,
299            uvec2(self.config.width, self.config.height),
300        );
301    }
302
303    fn render(&mut self) {
304        let texture = match self.surface.get_current_texture() {
305            wgpu::CurrentSurfaceTexture::Success(texture)
306            | wgpu::CurrentSurfaceTexture::Suboptimal(texture) => texture,
307            e => {
308                log::error!("Failed to get current texture: {e:?}");
309                return;
310            }
311        };
312        let texture_view = texture.texture.create_view(&wgpu::TextureViewDescriptor {
313            label: Some("Texture View"),
314            format: Some(self.config.view_formats[0]),
315            ..Default::default()
316        });
317
318        let mut encoder = self
319            .device
320            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
321                label: Some("Command Encoder"),
322            });
323
324        self.viewer.render(&mut encoder, &texture_view);
325
326        if !self.immediate && self.selector_type.is_some() {
327            self.viewport_texture_overlay_renderer
328                .render(&mut encoder, &texture_view);
329        }
330
331        self.queue.submit(std::iter::once(encoder.finish()));
332        if let Err(e) = self.device.poll(wgpu::PollType::wait_indefinitely()) {
333            log::error!("Failed to poll device: {e:?}");
334        }
335        texture.present();
336    }
337
338    fn resize(&mut self, size: winit::dpi::PhysicalSize<u32>) {
339        if size.width > 0 && size.height > 0 {
340            self.config.width = size.width;
341            self.config.height = size.height;
342            self.surface.configure(&self.device, &self.config);
343
344            // Update selector viewport texture
345            self.selector
346                .resize(&self.device, UVec2::new(size.width, size.height));
347
348            // Update viewport selection bundle
349            let viewport_selection_bind_group =
350                self.viewport_selection_modifier.modifier.selection.bundles[0]
351                    .create_bind_group(
352                        &self.device,
353                        // index 0 is the Gaussians buffer, so we use 1,
354                        // see docs of create_viewport_bundle
355                        1,
356                        [
357                            self.viewer.camera_buffer.buffer().as_entire_binding(),
358                            wgpu::BindingResource::TextureView(self.selector.texture().view()),
359                        ],
360                    )
361                    .expect("bind group");
362
363            // Update viewport selection modifier selection expr
364            self.viewport_selection_modifier.modifier.selection_expr =
365                gs::editor::SelectionExpr::Selection(0, vec![viewport_selection_bind_group]);
366
367            // Update viewport texture overlay renderer
368            self.viewport_texture_overlay_renderer
369                .update_bind_group(&self.device, self.selector.texture());
370        }
371    }
372}
373
374impl System {
375    fn update_selection(&mut self, input: &core::Input, _delta_time: f32) {
376        let mut encoder = self
377            .device
378            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
379                label: Some("Command Encoder"),
380            });
381
382        if input
383            .pressed_mouse
384            .contains(&winit::event::MouseButton::Left)
385        {
386            self.selector.start(&self.queue, input.mouse_pos);
387        }
388
389        if input.held_mouse.contains(&winit::event::MouseButton::Left) {
390            self.selector.update(&self.queue, input.mouse_pos);
391
392            if self.immediate {
393                self.apply_selection(&mut encoder);
394            }
395        }
396
397        if input
398            .released_mouse
399            .contains(&winit::event::MouseButton::Left)
400        {
401            self.apply_selection(&mut encoder);
402            self.selector.clear(&mut encoder);
403        }
404
405        if input.held_mouse.contains(&winit::event::MouseButton::Left) {
406            self.selector.render(&mut encoder);
407        }
408
409        self.queue.submit(std::iter::once(encoder.finish()));
410        if let Err(e) = self.device.poll(wgpu::PollType::wait_indefinitely()) {
411            log::error!("Failed to poll device: {e:?}");
412        }
413    }
414
415    fn apply_selection(&mut self, encoder: &mut wgpu::CommandEncoder) {
416        if self.filter {
417            // In filter mode, we only evaluate the selection to the viewer's selection buffer
418            // and do not modify anything.
419            self.viewport_selection_modifier
420                .try_apply_with(
421                    encoder,
422                    &self.viewer.gaussians_buffer,
423                    |encoder, modifier, gaussians| {
424                        modifier.selection.evaluate(
425                            &self.device,
426                            encoder,
427                            &modifier.selection_expr,
428                            &self.viewer.selection_buffer,
429                            &self.viewer.model_transform_buffer,
430                            &self.viewer.gaussian_transform_buffer,
431                            gaussians,
432                        );
433                    },
434                )
435                .expect("apply selection modifier");
436        } else {
437            if self.inverted {
438                self.viewport_selection_modifier
439                    .modifier
440                    .selection_expr
441                    .update_with(gs::editor::SelectionExpr::complement);
442            }
443
444            self.viewport_selection_modifier.apply(
445                &self.device,
446                encoder,
447                &self.viewer.gaussians_buffer,
448                &self.viewer.model_transform_buffer,
449                &self.viewer.gaussian_transform_buffer,
450            );
451        }
452    }
453
454    fn update_movement(&mut self, input: &core::Input, delta_time: f32) {
455        // Camera movement
456        const SPEED: f32 = 1.0;
457
458        let mut forward = 0.0;
459        if input.held_keys.contains(&KeyCode::KeyW) {
460            forward += SPEED * delta_time;
461        }
462        if input.held_keys.contains(&KeyCode::KeyS) {
463            forward -= SPEED * delta_time;
464        }
465
466        let mut right = 0.0;
467        if input.held_keys.contains(&KeyCode::KeyD) {
468            right += SPEED * delta_time;
469        }
470        if input.held_keys.contains(&KeyCode::KeyA) {
471            right -= SPEED * delta_time;
472        }
473
474        self.camera.move_by(forward, right);
475
476        let mut up = 0.0;
477        if input.held_keys.contains(&KeyCode::Space) {
478            up += SPEED * delta_time;
479        }
480        if input.held_keys.contains(&KeyCode::ShiftLeft) {
481            up -= SPEED * delta_time;
482        }
483
484        self.camera.move_up(up);
485
486        // Camera rotation
487        const SENSITIVITY: f32 = 0.15;
488
489        let yaw = input.mouse_diff.x * SENSITIVITY * delta_time;
490        let pitch = input.mouse_diff.y * SENSITIVITY * delta_time;
491
492        self.camera.pitch_by(-pitch);
493        self.camera.yaw_by(-yaw);
494    }
495}