custom_3d_three-d.rs
1 //! This demo shows how to embed 3D rendering using [`three-d`](https://github.com/asny/three-d) in `eframe`. 2 //! 3 //! Any 3D library built on top of [`glow`](https://github.com/grovesNL/glow) can be used in `eframe`. 4 //! 5 //! Alternatively you can render 3D stuff to a texture and display it using [`egui::Ui::image`]. 6 //! 7 //! If you are content of having egui sit on top of a 3D background, take a look at: 8 //! 9 //! * [`bevy_egui`](https://github.com/mvlabat/bevy_egui) 10 //! * [`three-d`](https://github.com/asny/three-d) 11 12 #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release 13 14 use eframe::egui; 15 16 fn main() { 17 let options = eframe::NativeOptions { 18 initial_window_size: Some(egui::vec2(550.0, 610.0)), 19 multisampling: 8, 20 ..Default::default() 21 }; 22 eframe::run_native( 23 "Custom 3D painting in eframe!", 24 options, 25 Box::new(|cc| Box::new(MyApp::new(cc))), 26 ); 27 } 28 29 struct MyApp { 30 angle: f32, 31 } 32 33 impl MyApp { 34 fn new(_cc: &eframe::CreationContext<'_>) -> Self { 35 Self { angle: 0.2 } 36 } 37 } 38 39 impl eframe::App for MyApp { 40 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { 41 egui::CentralPanel::default().show(ctx, |ui| { 42 egui::widgets::global_dark_light_mode_buttons(ui); 43 44 ui.horizontal(|ui| { 45 ui.spacing_mut().item_spacing.x = 0.0; 46 ui.label("The triangle is being painted using "); 47 ui.hyperlink_to("three-d", "https://github.com/asny/three-d"); 48 ui.label("."); 49 }); 50 51 egui::ScrollArea::both().show(ui, |ui| { 52 egui::Frame::canvas(ui.style()).show(ui, |ui| { 53 self.custom_painting(ui); 54 }); 55 ui.label("Drag to rotate!"); 56 }); 57 }); 58 } 59 } 60 61 impl MyApp { 62 fn custom_painting(&mut self, ui: &mut egui::Ui) { 63 let (rect, response) = 64 ui.allocate_exact_size(egui::Vec2::splat(512.0), egui::Sense::drag()); 65 66 self.angle += response.drag_delta().x * 0.01; 67 68 // Clone locals so we can move them into the paint callback: 69 let angle = self.angle; 70 71 let callback = egui::PaintCallback { 72 rect, 73 callback: std::sync::Arc::new(move |info, render_ctx| { 74 if let Some(painter) = render_ctx.downcast_ref::<egui_glow::Painter>() { 75 with_three_d_context(painter.gl(), |three_d| { 76 paint_with_three_d(three_d, info, angle); 77 }); 78 } else { 79 eprintln!("Can't do custom painting because we are not using a glow context"); 80 } 81 }), 82 }; 83 ui.painter().add(callback); 84 } 85 } 86 87 /// We get a [`glow::Context`] from `eframe`, but we want a [`three_d::Context`]. 88 /// 89 /// Sadly we can't just create a [`three_d::Context`] in [`MyApp::new`] and pass it 90 /// to the [`egui::PaintCallback`] because [`three_d::Context`] isn't `Send+Sync`, which 91 /// [`egui::PaintCallback`] is. 92 fn with_three_d_context<R>( 93 gl: &std::rc::Rc<glow::Context>, 94 f: impl FnOnce(&three_d::Context) -> R, 95 ) -> R { 96 use std::cell::RefCell; 97 thread_local! { 98 pub static THREE_D: RefCell<Option<three_d::Context>> = RefCell::new(None); 99 } 100 101 THREE_D.with(|three_d| { 102 let mut three_d = three_d.borrow_mut(); 103 let three_d = 104 three_d.get_or_insert_with(|| three_d::Context::from_gl_context(gl.clone()).unwrap()); 105 f(three_d) 106 }) 107 } 108 109 fn paint_with_three_d(three_d: &three_d::Context, info: &egui::PaintCallbackInfo, angle: f32) { 110 // Based on https://github.com/asny/three-d/blob/master/examples/triangle/src/main.rs 111 use three_d::*; 112 113 let viewport = Viewport { 114 x: info.viewport_left_px().round() as _, 115 y: info.viewport_from_bottom_px().round() as _, 116 width: info.viewport_width_px().round() as _, 117 height: info.viewport_height_px().round() as _, 118 }; 119 120 let camera = Camera::new_perspective( 121 three_d, 122 viewport, 123 vec3(0.0, 0.0, 2.0), 124 vec3(0.0, 0.0, 0.0), 125 vec3(0.0, 1.0, 0.0), 126 degrees(45.0), 127 0.1, 128 10.0, 129 ) 130 .unwrap(); 131 132 // Create a CPU-side mesh consisting of a single colored triangle 133 let positions = vec![ 134 vec3(0.5, -0.5, 0.0), // bottom right 135 vec3(-0.5, -0.5, 0.0), // bottom left 136 vec3(0.0, 0.5, 0.0), // top 137 ]; 138 let colors = vec![ 139 Color::new(255, 0, 0, 255), // bottom right 140 Color::new(0, 255, 0, 255), // bottom left 141 Color::new(0, 0, 255, 255), // top 142 ]; 143 let cpu_mesh = CpuMesh { 144 positions: Positions::F32(positions), 145 colors: Some(colors), 146 ..Default::default() 147 }; 148 149 // Construct a model, with a default color material, thereby transferring the mesh data to the GPU 150 let mut model = Model::new(three_d, &cpu_mesh).unwrap(); 151 152 // Set the current transformation of the triangle 153 model.set_transformation(Mat4::from_angle_y(radians(angle))); 154 155 // Render the triangle with the color material which uses the per vertex colors defined at construction 156 model.render(&camera, &[]).unwrap(); 157 }