/ eframe / examples / custom_3d_three-d.rs
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  }