1use 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#[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 #[arg(short, long)]
49 model: String,
50
51 #[arg(short, long)]
53 filter: bool,
54
55 #[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#[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 = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
104
105 log::debug!("Creating window surface");
106 let surface = instance.create_surface(window.clone()).expect("surface");
107
108 log::debug!("Requesting adapter");
109 let adapter = instance
110 .request_adapter(&wgpu::RequestAdapterOptions {
111 power_preference: wgpu::PowerPreference::HighPerformance,
112 compatible_surface: Some(&surface),
113 force_fallback_adapter: false,
114 })
115 .await
116 .expect("adapter");
117
118 log::debug!("Requesting device");
119 let (device, queue) = adapter
120 .request_device(&wgpu::DeviceDescriptor {
121 label: Some("Device"),
122 required_limits: adapter.limits(),
123 ..Default::default()
124 })
125 .await
126 .expect("device");
127
128 let surface_caps = surface.get_capabilities(&adapter);
129 let surface_format = surface_caps.formats[0];
130 let config = wgpu::SurfaceConfiguration {
131 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
132 format: surface_format,
133 width: size.width.max(1),
134 height: size.height.max(1),
135 present_mode: surface_caps.present_modes[0],
136 alpha_mode: surface_caps.alpha_modes[0],
137 view_formats: vec![surface_format.remove_srgb_suffix()],
138 desired_maximum_frame_latency: 2,
139 };
140
141 log::debug!("Configuring surface");
142 surface.configure(&device, &config);
143
144 log::debug!("Creating gaussians");
145 let gaussians = [GaussiansSource::Ply, GaussiansSource::Spz]
146 .into_iter()
147 .find_map(|source| gs::core::Gaussians::read_from_file(model_path, source).ok())
148 .expect("gaussians");
149
150 log::debug!("Creating camera");
151 let camera = gs::Camera::new(0.1..1e4, 60f32.to_radians());
152
153 log::debug!("Creating viewer");
154 let mut viewer = gs::Viewer::new_with_options(
155 &device,
156 config.view_formats[0],
157 &gaussians,
158 gs::ViewerCreateOptions {
159 gaussians_buffer_usage:
160 gs::core::GaussiansBuffer::<gs::DefaultGaussianPod>::DEFAULT_USAGES
161 | wgpu::BufferUsages::COPY_SRC,
162 ..Default::default()
163 },
164 )
165 .expect("viewer");
166 viewer.update_model_transform(
167 &queue,
168 Vec3::ZERO,
169 Quat::from_axis_angle(Vec3::Z, 180f32.to_radians()),
170 Vec3::ONE,
171 );
172
173 log::debug!("Creating selector");
174 let mut selector = gs::selection::ViewportSelector::new(
175 &device,
176 &queue,
177 UVec2::new(size.width, size.height),
178 &viewer.camera_buffer,
179 )
180 .expect("selector");
181 selector.selector_type = gs::selection::ViewportSelectorType::Brush;
182
183 log::debug!("Creating selection viewport selection modifier");
184 let mut viewport_selection_modifier = gs::editor::NonDestructiveModifier::new(
185 &device,
186 &queue,
187 gs::editor::BasicSelectionModifier::new_with_basic_modifier(
188 &device,
189 &viewer.gaussians_buffer,
190 &viewer.model_transform_buffer,
191 &viewer.gaussian_transform_buffer,
192 vec![gs::selection::create_viewport_bundle::<
193 gs::DefaultGaussianPod,
194 >(&device)],
195 ),
196 &viewer.gaussians_buffer,
197 )
198 .expect("modifier");
199
200 let viewport_selection_bind_group = viewport_selection_modifier.modifier.selection.bundles
201 [0]
202 .create_bind_group(
203 &device,
204 1,
207 [
208 viewer.camera_buffer.buffer().as_entire_binding(),
209 wgpu::BindingResource::TextureView(selector.texture().view()),
210 ],
211 )
212 .expect("bind group");
213
214 viewport_selection_modifier.modifier.selection_expr =
215 gs::editor::SelectionExpr::Selection(0, vec![viewport_selection_bind_group]);
216
217 viewport_selection_modifier .modifier .modifier .basic_color_modifiers_buffer
221 .update_with_pod(
222 &queue,
223 &gs::editor::BasicColorModifiersPod {
224 rgb_or_hsv: BasicColorRgbOverrideOrHsvModifiersPod::new_rgb_override(
225 Vec3::new(1.0, 1.0, 0.0),
226 ),
227 ..Default::default()
228 },
229 );
230
231 log::debug!("Creating selection viewport texture overlay renderer");
232 let viewport_texture_overlay_renderer =
233 utils::selection::ViewportTextureOverlayRenderer::new(
234 &device,
235 config.view_formats[0],
236 selector.texture(),
237 );
238
239 log::info!("System initialized");
240
241 Self {
242 surface,
243 device,
244 queue,
245 config,
246
247 filter,
248 immediate,
249 inverted: filter,
250 selector_type: None,
251
252 camera,
253 gaussians,
254 viewer,
255 selector,
256
257 viewport_selection_modifier,
258 viewport_texture_overlay_renderer,
259 }
260 }
261
262 fn update(&mut self, input: &core::Input, delta_time: f32) {
263 if input.pressed_keys.contains(&KeyCode::KeyN) {
265 self.selector_type = None;
266 log::info!("Selector: None");
267 }
268 if input.pressed_keys.contains(&KeyCode::KeyR) {
269 self.selector_type = Some(gs::selection::ViewportSelectorType::Rectangle);
270 log::info!("Selector: Rectangle");
271 self.selector.selector_type = gs::selection::ViewportSelectorType::Rectangle;
272 }
273 if input.pressed_keys.contains(&KeyCode::KeyB) {
274 self.selector_type = Some(gs::selection::ViewportSelectorType::Brush);
275 log::info!("Selector: Brush");
276 self.selector.selector_type = gs::selection::ViewportSelectorType::Brush;
277 }
278 if input.pressed_keys.contains(&KeyCode::KeyI) {
279 self.inverted = !self.inverted;
280 log::info!("Inverted: {}", self.inverted);
281 if self.filter {
282 self.viewer
283 .invert_selection_buffer
284 .update(&self.queue, self.inverted);
285 }
286 }
287
288 if self.selector_type.is_some() {
289 self.update_selection(input, delta_time);
290 } else {
291 self.update_movement(input, delta_time);
292 }
293
294 self.viewer.update_camera(
296 &self.queue,
297 &self.camera,
298 uvec2(self.config.width, self.config.height),
299 );
300 }
301
302 fn render(&mut self) {
303 let texture = match self.surface.get_current_texture() {
304 Ok(texture) => texture,
305 Err(e) => {
306 log::error!("Failed to get current texture: {e:?}");
307 return;
308 }
309 };
310 let texture_view = texture.texture.create_view(&wgpu::TextureViewDescriptor {
311 label: Some("Texture View"),
312 format: Some(self.config.view_formats[0]),
313 ..Default::default()
314 });
315
316 let mut encoder = self
317 .device
318 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
319 label: Some("Command Encoder"),
320 });
321
322 self.viewer.render(&mut encoder, &texture_view);
323
324 if !self.immediate && self.selector_type.is_some() {
325 self.viewport_texture_overlay_renderer
326 .render(&mut encoder, &texture_view);
327 }
328
329 self.queue.submit(std::iter::once(encoder.finish()));
330 if let Err(e) = self.device.poll(wgpu::PollType::wait_indefinitely()) {
331 log::error!("Failed to poll device: {e:?}");
332 }
333 texture.present();
334 }
335
336 fn resize(&mut self, size: winit::dpi::PhysicalSize<u32>) {
337 if size.width > 0 && size.height > 0 {
338 self.config.width = size.width;
339 self.config.height = size.height;
340 self.surface.configure(&self.device, &self.config);
341
342 self.selector
344 .resize(&self.device, UVec2::new(size.width, size.height));
345
346 let viewport_selection_bind_group =
348 self.viewport_selection_modifier.modifier.selection.bundles[0]
349 .create_bind_group(
350 &self.device,
351 1,
354 [
355 self.viewer.camera_buffer.buffer().as_entire_binding(),
356 wgpu::BindingResource::TextureView(self.selector.texture().view()),
357 ],
358 )
359 .expect("bind group");
360
361 self.viewport_selection_modifier.modifier.selection_expr =
363 gs::editor::SelectionExpr::Selection(0, vec![viewport_selection_bind_group]);
364
365 self.viewport_texture_overlay_renderer
367 .update_bind_group(&self.device, self.selector.texture());
368 }
369 }
370}
371
372impl System {
373 fn update_selection(&mut self, input: &core::Input, _delta_time: f32) {
374 let mut encoder = self
375 .device
376 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
377 label: Some("Command Encoder"),
378 });
379
380 if input
381 .pressed_mouse
382 .contains(&winit::event::MouseButton::Left)
383 {
384 self.selector.start(&self.queue, input.mouse_pos);
385 }
386
387 if input.held_mouse.contains(&winit::event::MouseButton::Left) {
388 self.selector.update(&self.queue, input.mouse_pos);
389
390 if self.immediate {
391 self.apply_selection(&mut encoder);
392 }
393 }
394
395 if input
396 .released_mouse
397 .contains(&winit::event::MouseButton::Left)
398 {
399 self.apply_selection(&mut encoder);
400 self.selector.clear(&mut encoder);
401 }
402
403 if input.held_mouse.contains(&winit::event::MouseButton::Left) {
404 self.selector.render(&mut encoder);
405 }
406
407 self.queue.submit(std::iter::once(encoder.finish()));
408 if let Err(e) = self.device.poll(wgpu::PollType::wait_indefinitely()) {
409 log::error!("Failed to poll device: {e:?}");
410 }
411 }
412
413 fn apply_selection(&mut self, encoder: &mut wgpu::CommandEncoder) {
414 if self.filter {
415 self.viewport_selection_modifier
418 .try_apply_with(
419 encoder,
420 &self.viewer.gaussians_buffer,
421 |encoder, modifier, gaussians| {
422 modifier.selection.evaluate(
423 &self.device,
424 encoder,
425 &modifier.selection_expr,
426 &self.viewer.selection_buffer,
427 &self.viewer.model_transform_buffer,
428 &self.viewer.gaussian_transform_buffer,
429 gaussians,
430 );
431 },
432 )
433 .expect("apply selection modifier");
434 } else {
435 if self.inverted {
436 self.viewport_selection_modifier
437 .modifier
438 .selection_expr
439 .update_with(gs::editor::SelectionExpr::complement);
440 }
441
442 self.viewport_selection_modifier.apply(
443 &self.device,
444 encoder,
445 &self.viewer.gaussians_buffer,
446 &self.viewer.model_transform_buffer,
447 &self.viewer.gaussian_transform_buffer,
448 );
449 }
450 }
451
452 fn update_movement(&mut self, input: &core::Input, delta_time: f32) {
453 const SPEED: f32 = 1.0;
455
456 let mut forward = 0.0;
457 if input.held_keys.contains(&KeyCode::KeyW) {
458 forward += SPEED * delta_time;
459 }
460 if input.held_keys.contains(&KeyCode::KeyS) {
461 forward -= SPEED * delta_time;
462 }
463
464 let mut right = 0.0;
465 if input.held_keys.contains(&KeyCode::KeyD) {
466 right += SPEED * delta_time;
467 }
468 if input.held_keys.contains(&KeyCode::KeyA) {
469 right -= SPEED * delta_time;
470 }
471
472 self.camera.move_by(forward, right);
473
474 let mut up = 0.0;
475 if input.held_keys.contains(&KeyCode::Space) {
476 up += SPEED * delta_time;
477 }
478 if input.held_keys.contains(&KeyCode::ShiftLeft) {
479 up -= SPEED * delta_time;
480 }
481
482 self.camera.move_up(up);
483
484 const SENSITIVITY: f32 = 0.15;
486
487 let yaw = input.mouse_diff.x * SENSITIVITY * delta_time;
488 let pitch = input.mouse_diff.y * SENSITIVITY * delta_time;
489
490 self.camera.pitch_by(-pitch);
491 self.camera.yaw_by(-yaw);
492 }
493}