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 =
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 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 .modifier .modifier .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 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 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 self.selector
346 .resize(&self.device, UVec2::new(size.width, size.height));
347
348 let viewport_selection_bind_group =
350 self.viewport_selection_modifier.modifier.selection.bundles[0]
351 .create_bind_group(
352 &self.device,
353 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 self.viewport_selection_modifier.modifier.selection_expr =
365 gs::editor::SelectionExpr::Selection(0, vec![viewport_selection_bind_group]);
366
367 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 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 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 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}