1use std::f32::consts::PI;
2use std::os::raw::c_void;
3use std::path::Path;
4use std::process;
5use std::time::Instant;
6
7use cgmath::{Deg, Point3};
8use collision::Aabb;
9use gl;
10use gltf;
11use glutin;
12use glutin::dpi::PhysicalSize;
13use glutin::ElementState::*;
14use glutin::{
15 Api, GlContext, GlProfile, GlRequest, MouseButton, MouseScrollDelta, VirtualKeyCode,
16 WindowEvent,
17};
18
19use image::DynamicImage;
20use log::{error, info, warn};
21
22use crate::controls::CameraMovement::*;
23use crate::controls::{NavState, OrbitControls};
24use crate::framebuffer::Framebuffer;
25use crate::importdata::ImportData;
26use crate::render::math::*;
27use crate::render::*;
28use crate::utils::{gl_check_error, print_context_info, print_elapsed, FrameTimer};
29
30#[derive(Copy, Clone)]
38pub struct CameraOptions {
39 pub index: i32,
40 pub position: Option<Vector3>,
41 pub target: Option<Vector3>,
42 pub fovy: Deg<f32>,
43 pub straight: bool,
44}
45
46pub struct GltfViewer {
47 size: PhysicalSize,
48 dpi_factor: f64,
49
50 orbit_controls: OrbitControls,
51 events_loop: Option<glutin::EventsLoop>,
52 gl_window: Option<glutin::GlWindow>,
53
54 root: Root,
56 scene: Scene,
57
58 delta_time: f64, last_frame: Instant,
60
61 render_timer: FrameTimer,
62}
63
64impl GltfViewer {
67 pub fn new(
68 source: &str,
69 width: u32,
70 height: u32,
71 headless: bool,
72 visible: bool,
73 camera_options: CameraOptions,
74 scene_index: usize,
75 ) -> GltfViewer {
76 let gl_request = GlRequest::Specific(Api::OpenGl, (3, 3));
77 let gl_profile = GlProfile::Core;
78 let (events_loop, gl_window, dpi_factor, inner_size) = if headless {
79 let headless_context = glutin::HeadlessRendererBuilder::new(width, height)
80 .build()
83 .unwrap();
84 unsafe { headless_context.make_current().unwrap() }
85 gl::load_with(|symbol| headless_context.get_proc_address(symbol) as *const _);
86 let framebuffer = Framebuffer::new(width, height);
87 framebuffer.bind();
88 unsafe {
89 gl::Viewport(0, 0, width as i32, height as i32);
90 }
91
92 (
93 None,
94 None,
95 1.0,
96 PhysicalSize::new(width as f64, height as f64),
97 ) } else {
99 let events_loop = glutin::EventsLoop::new();
101 let window_size = glutin::dpi::LogicalSize::new(width as f64, height as f64);
102
103 let window = glutin::WindowBuilder::new()
105 .with_title("gltf-viewer")
106 .with_dimensions(window_size)
107 .with_visibility(visible);
108
109 let context = glutin::ContextBuilder::new()
110 .with_gl(gl_request)
111 .with_gl_profile(gl_profile)
112 .with_vsync(true);
113 let gl_window = glutin::GlWindow::new(window, context, &events_loop).unwrap();
114
115 let dpi_factor = gl_window.get_hidpi_factor();
117 let inner_size = gl_window.get_inner_size().unwrap().to_physical(dpi_factor);
118
119 unsafe {
120 gl_window.make_current().unwrap();
121 }
122
123 gl::load_with(|symbol| gl_window.get_proc_address(symbol) as *const _);
125
126 (Some(events_loop), Some(gl_window), dpi_factor, inner_size)
127 };
128
129 let mut orbit_controls = OrbitControls::new(Point3::new(0.0, 0.0, 2.0), inner_size);
130 orbit_controls.camera = Camera::default();
131 orbit_controls.camera.fovy = camera_options.fovy;
132 orbit_controls
133 .camera
134 .update_aspect_ratio(inner_size.width as f32 / inner_size.height as f32); unsafe {
137 print_context_info();
138
139 gl::ClearColor(0.0, 1.0, 0.0, 1.0); gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
141
142 if headless || !visible {
143 gl::ClearColor(0.0, 0.0, 0.0, 0.0);
145 } else {
146 gl::ClearColor(0.1, 0.2, 0.3, 1.0);
147 }
148
149 gl::Enable(gl::DEPTH_TEST);
150
151 };
155
156 let (root, scene) = Self::load(source, scene_index);
157 let mut viewer = GltfViewer {
158 size: inner_size,
159 dpi_factor,
160
161 orbit_controls,
162
163 events_loop,
164 gl_window,
165
166 root,
167 scene,
168
169 delta_time: 0.0, last_frame: Instant::now(),
171
172 render_timer: FrameTimer::new("rendering", 300),
173 };
174 unsafe {
175 gl_check_error!();
176 };
177
178 if camera_options.index != 0
179 && camera_options.index >= viewer.root.camera_nodes.len() as i32
180 {
181 error!(
182 "No camera with index {} found in glTF file (max: {})",
183 camera_options.index,
184 viewer.root.camera_nodes.len() as i32 - 1
185 );
186 process::exit(2)
187 }
188 if !viewer.root.camera_nodes.is_empty() && camera_options.index != -1 {
189 let cam_node = &viewer.root.get_camera_node(camera_options.index as usize);
190 let cam_node_info = format!("{} ({:?})", cam_node.index, cam_node.name);
191 let cam = cam_node.camera.as_ref().unwrap();
192 info!(
193 "Using camera {} on node {}",
194 cam.description(),
195 cam_node_info
196 );
197 viewer
198 .orbit_controls
199 .set_camera(cam, &cam_node.final_transform);
200
201 if camera_options.position.is_some() || camera_options.target.is_some() {
202 warn!("Ignoring --cam-pos / --cam-target since --cam-index is given.")
203 }
204 } else {
205 info!("Determining camera view from bounding box");
206 viewer.set_camera_from_bounds(camera_options.straight);
207
208 if let Some(p) = camera_options.position {
209 viewer.orbit_controls.position = Point3::from_vec(p)
210 }
211 if let Some(target) = camera_options.target {
212 viewer.orbit_controls.target = Point3::from_vec(target)
213 }
214 }
215
216 viewer
217 }
218
219 pub fn load(source: &str, scene_index: usize) -> (Root, Scene) {
220 let mut start_time = Instant::now();
221 if source.starts_with("http") {
224 panic!("not implemented: HTTP support temporarily removed.")
225 }
231 let (doc, buffers, images) = match gltf::import(source) {
233 Ok(tuple) => tuple,
234 Err(err) => {
235 error!("glTF import failed: {:?}", err);
236 if let gltf::Error::Io(_) = err {
237 error!("Hint: Are the .bin file(s) referenced by the .gltf file available?")
238 }
239 process::exit(1)
240 }
241 };
242 let imp = ImportData {
243 doc,
244 buffers,
245 images,
246 };
247
248 print_elapsed("Imported glTF in ", start_time);
249 start_time = Instant::now();
250
251 if scene_index >= imp.doc.scenes().len() {
253 error!(
254 "Scene index too high - file has only {} scene(s)",
255 imp.doc.scenes().len()
256 );
257 process::exit(3)
258 }
259 let base_path = Path::new(source);
260 let mut root = Root::from_gltf(&imp, base_path);
261 let scene = Scene::from_gltf(&imp.doc.scenes().nth(scene_index).unwrap(), &mut root);
262 print_elapsed(
263 &format!(
264 "Loaded scene with {} nodes, {} meshes in ",
265 imp.doc.nodes().count(),
266 imp.doc.meshes().len()
267 ),
268 start_time,
269 );
270
271 (root, scene)
272 }
273
274 fn set_camera_from_bounds(&mut self, straight: bool) {
276 let bounds = &self.scene.bounds;
277 let size = (bounds.max - bounds.min).magnitude();
278 let center = bounds.center();
279
280 let cam_pos = if straight {
282 Point3::new(center.x, center.y, center.z + size * 0.75)
283 } else {
284 Point3::new(
285 center.x + size / 2.0,
286 center.y + size / 5.0,
287 center.z + size / 2.0,
288 )
289 };
290
291 self.orbit_controls.position = cam_pos;
292 self.orbit_controls.target = center;
293 self.orbit_controls.camera.znear = size / 100.0;
294 self.orbit_controls.camera.zfar = Some(size * 20.0);
295 self.orbit_controls.camera.update_projection_matrix();
296 }
297
298 pub fn start_render_loop(&mut self) {
299 loop {
300 self.delta_time = f64::from(self.last_frame.elapsed().subsec_nanos()) / 1_000_000_000.0;
303 self.last_frame = Instant::now();
304
305 let keep_running = process_events(
307 &mut self.events_loop.as_mut().unwrap(),
308 self.gl_window.as_mut().unwrap(),
309 &mut self.orbit_controls,
310 &mut self.dpi_factor,
311 &mut self.size,
312 );
313 if !keep_running {
314 unsafe {
315 gl_check_error!();
316 } break;
318 }
319
320 self.orbit_controls.frame_update(self.delta_time); self.draw();
323
324 self.gl_window.as_ref().unwrap().swap_buffers().unwrap();
325 }
326 }
327
328 pub fn draw(&mut self) {
330 unsafe {
332 self.render_timer.start();
333
334 gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
335
336 let cam_params = self.orbit_controls.camera_params();
337 self.scene.draw(&mut self.root, &cam_params);
338
339 self.render_timer.end();
340 }
341 }
342
343 pub fn screenshot(&mut self, filename: &str) {
344 self.draw();
345
346 let mut img = DynamicImage::new_rgba8(self.size.width as u32, self.size.height as u32);
347 unsafe {
348 let pixels = img.as_mut_rgba8().unwrap();
349 gl::PixelStorei(gl::PACK_ALIGNMENT, 1);
350 gl::ReadPixels(
351 0,
352 0,
353 self.size.width as i32,
354 self.size.height as i32,
355 gl::RGBA,
356 gl::UNSIGNED_BYTE,
357 pixels.as_mut_ptr() as *mut c_void,
358 );
359 gl_check_error!();
360 }
361
362 let img = img.flipv();
363 if let Err(err) = img.save(filename) {
364 error!("{}", err);
365 } else {
366 println!(
367 "Saved {}x{} screenshot to {}",
368 self.size.width, self.size.height, filename
369 );
370 }
371 }
372 pub fn multiscreenshot(&mut self, filename: &str, count: u32) {
373 let min_angle: f32 = 0.0;
374 let max_angle: f32 = 2.0 * PI;
375 let increment_angle: f32 = ((max_angle - min_angle) / (count as f32)) as f32;
376 let suffix_length = count.to_string().len();
377 for i in 1..=count {
378 self.orbit_controls.rotate_object(increment_angle);
379 let dot = filename.rfind('.').unwrap_or_else(|| filename.len());
380 let mut actual_name = filename.to_string();
381 actual_name.insert_str(
382 dot,
383 &format!("_{:0suffix_length$}", i, suffix_length = suffix_length),
384 );
385 self.screenshot(&actual_name[..]);
386 }
387 }
388
389 pub fn screenshot_save(&mut self) -> DynamicImage {
390 self.draw();
391
392 let mut img = DynamicImage::new_rgba8(self.size.width as u32, self.size.height as u32);
393 unsafe {
394 let pixels = img.as_mut_rgba8().unwrap();
395 gl::PixelStorei(gl::PACK_ALIGNMENT, 1);
396 gl::ReadPixels(
397 0,
398 0,
399 self.size.width as i32,
400 self.size.height as i32,
401 gl::RGBA,
402 gl::UNSIGNED_BYTE,
403 pixels.as_mut_ptr() as *mut c_void,
404 );
405 gl_check_error!();
406 }
407
408 let img = img.flipv();
409 img
410 }
411
412 pub fn multiscreenshot_save(&mut self, count: u32) -> Vec<DynamicImage> {
413 let min_angle: f32 = 0.0;
414 let max_angle: f32 = 2.0 * PI;
415 let increment_angle: f32 = ((max_angle - min_angle) / (count as f32)) as f32;
416 let mut images: Vec<DynamicImage> = vec![];
417 for _ in 1..=count {
418 self.orbit_controls.rotate_object(increment_angle);
419 let image = self.screenshot_save();
420 images.push(image);
421 }
422 images
423 }
424}
425
426#[allow(clippy::too_many_arguments)]
427fn process_events(
428 events_loop: &mut glutin::EventsLoop,
429 gl_window: &glutin::GlWindow,
430 mut orbit_controls: &mut OrbitControls,
431 dpi_factor: &mut f64,
432 size: &mut PhysicalSize,
433) -> bool {
434 let mut keep_running = true;
435 #[allow(clippy::single_match)]
436 events_loop.poll_events(|event| {
437 match event {
438 glutin::Event::WindowEvent { event, .. } => match event {
439 WindowEvent::CloseRequested => {
440 keep_running = false;
441 }
442 WindowEvent::Destroyed => {
443 panic!("WindowEvent::Destroyed, unimplemented.");
445 }
446 WindowEvent::Resized(logical) => {
447 let ph = logical.to_physical(*dpi_factor);
448 gl_window.resize(ph);
449
450 unsafe {
453 gl::Viewport(0, 0, ph.width as i32, ph.height as i32);
454 }
455
456 *size = ph;
457 orbit_controls
458 .camera
459 .update_aspect_ratio((ph.width / ph.height) as f32);
460 orbit_controls.screen_size = ph;
461 }
462 WindowEvent::HiDpiFactorChanged(f) => {
463 *dpi_factor = f;
464 }
465 WindowEvent::DroppedFile(_path_buf) => {
466 }
468 WindowEvent::MouseInput {
469 button,
470 state: Pressed,
471 ..
472 } => match button {
473 MouseButton::Left => {
474 orbit_controls.state = NavState::Rotating;
475 }
476 MouseButton::Right => {
477 orbit_controls.state = NavState::Panning;
478 }
479 _ => (),
480 },
481 WindowEvent::MouseInput {
482 button,
483 state: Released,
484 ..
485 } => match (button, orbit_controls.state.clone()) {
486 (MouseButton::Left, NavState::Rotating)
487 | (MouseButton::Right, NavState::Panning) => {
488 orbit_controls.state = NavState::None;
489 orbit_controls.handle_mouse_up();
490 }
491 _ => (),
492 },
493 WindowEvent::CursorMoved { position, .. } => {
494 let ph = position.to_physical(*dpi_factor);
495 orbit_controls.handle_mouse_move(ph)
496 }
497 WindowEvent::MouseWheel {
498 delta: MouseScrollDelta::PixelDelta(logical),
499 ..
500 } => {
501 let ph = logical.to_physical(*dpi_factor);
502 orbit_controls.process_mouse_scroll(ph.y as f32);
503 }
504 WindowEvent::MouseWheel {
505 delta: MouseScrollDelta::LineDelta(_rows, lines),
506 ..
507 } => {
508 orbit_controls.process_mouse_scroll(lines * 3.0);
509 }
510 WindowEvent::KeyboardInput { input, .. } => {
511 keep_running = process_input(input, &mut orbit_controls);
512 }
513 _ => (),
514 },
515 _ => (),
516 }
517 });
518
519 keep_running
520}
521
522fn process_input(input: glutin::KeyboardInput, controls: &mut OrbitControls) -> bool {
523 let pressed = match input.state {
524 Pressed => true,
525 Released => false,
526 };
527 if let Some(code) = input.virtual_keycode {
528 match code {
529 VirtualKeyCode::Escape if pressed => return false,
530 VirtualKeyCode::W | VirtualKeyCode::Up => controls.process_keyboard(FORWARD, pressed),
531 VirtualKeyCode::S | VirtualKeyCode::Down => {
532 controls.process_keyboard(BACKWARD, pressed)
533 }
534 VirtualKeyCode::A | VirtualKeyCode::Left => controls.process_keyboard(LEFT, pressed),
535 VirtualKeyCode::D | VirtualKeyCode::Right => controls.process_keyboard(RIGHT, pressed),
536 _ => (),
537 }
538 }
539 true
540}