diff --git a/.gitignore b/.gitignore index 14ea8159..bd26ae08 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /.*.json /.vscode /media/* +.DS_Store diff --git a/Cargo.lock b/Cargo.lock index 7c779ccd..069e3a13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -281,7 +281,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide 0.5.3", + "miniz_oxide", "object", "rustc-demangle", ] @@ -898,10 +898,13 @@ dependencies = [ name = "custom_3d_three-d" version = "0.1.0" dependencies = [ + "console_error_panic_hook", "eframe", "egui_glow", "glow", "three-d", + "wasm-bindgen", + "wasm-bindgen-futures", ] [[package]] @@ -1003,16 +1006,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7046468a81e6a002061c01e6a7c83139daf91b11c30e66795b13217c2d885c8b" -[[package]] -name = "deflate" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" -dependencies = [ - "adler32", - "byteorder", -] - [[package]] name = "deflate" version = "1.0.0" @@ -1123,7 +1116,7 @@ dependencies = [ "eframe", "egui_extras", "ehttp", - "image 0.24.2", + "image", "poll-promise", ] @@ -1258,7 +1251,7 @@ dependencies = [ "egui_demo_lib", "egui_extras", "ehttp", - "image 0.24.2", + "image", "poll-promise", "pollster", "serde", @@ -1290,7 +1283,7 @@ dependencies = [ "chrono", "document-features", "egui", - "image 0.24.2", + "image", "resvg", "serde", "tiny-skia", @@ -1308,7 +1301,7 @@ dependencies = [ "egui", "egui-winit", "glium", - "image 0.24.2", + "image", ] [[package]] @@ -1491,7 +1484,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ "crc32fast", - "miniz_oxide 0.5.3", + "miniz_oxide", ] [[package]] @@ -1786,16 +1779,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" -[[package]] -name = "gloo-timers" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "glow" version = "0.11.2" @@ -1948,7 +1931,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" dependencies = [ "num-traits", - "serde", "zerocopy", ] @@ -2033,22 +2015,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "image" -version = "0.23.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "jpeg-decoder 0.1.22", - "num-iter", - "num-rational 0.3.2", - "num-traits", - "png 0.16.8", -] - [[package]] name = "image" version = "0.24.2" @@ -2058,11 +2024,11 @@ dependencies = [ "bytemuck", "byteorder", "color_quant", - "jpeg-decoder 0.2.6", + "jpeg-decoder", "num-iter", - "num-rational 0.4.1", + "num-rational", "num-traits", - "png 0.17.5", + "png", ] [[package]] @@ -2143,12 +2109,6 @@ dependencies = [ "libc", ] -[[package]] -name = "jpeg-decoder" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" - [[package]] name = "jpeg-decoder" version = "0.2.6" @@ -2326,15 +2286,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" -dependencies = [ - "adler32", -] - [[package]] name = "miniz_oxide" version = "0.5.3" @@ -2552,17 +2503,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" version = "0.4.1" @@ -2872,18 +2812,6 @@ dependencies = [ "plotters-backend", ] -[[package]] -name = "png" -version = "0.16.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" -dependencies = [ - "bitflags", - "crc32fast", - "deflate 0.8.6", - "miniz_oxide 0.3.7", -] - [[package]] name = "png" version = "0.17.5" @@ -2892,8 +2820,8 @@ checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" dependencies = [ "bitflags", "crc32fast", - "deflate 1.0.0", - "miniz_oxide 0.5.3", + "deflate", + "miniz_oxide", ] [[package]] @@ -3150,10 +3078,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34489194784b86c03c3d688258e2ba73f3c82700ba4673ee2ecad5ae540b9438" dependencies = [ "gif", - "jpeg-decoder 0.2.6", + "jpeg-decoder", "log", "pico-args", - "png 0.17.5", + "png", "rgb", "svgfilters", "svgtypes", @@ -3167,7 +3095,7 @@ version = "0.1.0" dependencies = [ "eframe", "egui_extras", - "image 0.24.2", + "image", ] [[package]] @@ -3813,32 +3741,27 @@ dependencies = [ [[package]] name = "three-d" -version = "0.12.2" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cc0ef660c02244f00fc2d1759e67d30a3c282541458490de641f0e832c8d3c9" +checksum = "f7a5a1017829335f6761bdbae2daf3021a88314a1f8d5f188c9b62ce62e2fd31" dependencies = [ "cgmath", - "gloo-timers", "glow", - "half", - "js-sys", - "serde", + "instant", "thiserror", "three-d-asset", "wasm-bindgen", - "wasm-bindgen-futures", "web-sys", ] [[package]] name = "three-d-asset" -version = "0.1.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1886553d8b51093705b6d1a02ec2c41ac78260f68b40682d94d9976960d689" +checksum = "e953f34aa4169e098621a1ff23a68c47b7798711f7c769bf741248e850e93fbe" dependencies = [ "cgmath", "half", - "image 0.23.14", "thiserror", "web-sys", ] @@ -3875,7 +3798,7 @@ dependencies = [ "arrayvec 0.5.2", "bytemuck", "cfg-if", - "png 0.17.5", + "png", "safe_arch", ] @@ -4229,8 +4152,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" dependencies = [ "cfg-if", - "serde", - "serde_json", "wasm-bindgen-macro", ] diff --git a/examples/custom_3d_three-d/Cargo.toml b/examples/custom_3d_three-d/Cargo.toml index 27727647..8841fb46 100644 --- a/examples/custom_3d_three-d/Cargo.toml +++ b/examples/custom_3d_three-d/Cargo.toml @@ -7,9 +7,16 @@ edition = "2021" rust-version = "1.61" publish = false +[lib] +crate-type = ["cdylib", "rlib"] [dependencies] eframe = { path = "../../eframe", features = ["glow"] } egui_glow = { path = "../../egui_glow" } glow = "0.11" -three-d = { version = "0.12", default-features = false } # 2022-05-22 +three-d = { version = "0.13", default-features = false } + +[target.'cfg(target_arch = "wasm32")'.dependencies] # Web dependencies +wasm-bindgen = "0.2" # Core bindings +wasm-bindgen-futures = "0.4" # Core bindings +console_error_panic_hook = "0.1" # For logging diff --git a/examples/custom_3d_three-d/README.md b/examples/custom_3d_three-d/README.md index c6de521f..58c6d23f 100644 --- a/examples/custom_3d_three-d/README.md +++ b/examples/custom_3d_three-d/README.md @@ -14,3 +14,7 @@ If you are content of having egui sit on top of a 3D background, take a look at: ```sh cargo run -p custom_3d_three-d ``` + +``` +wasm-pack build examples/custom_3d_three-d --target web +``` diff --git a/examples/custom_3d_three-d/index.html b/examples/custom_3d_three-d/index.html new file mode 100644 index 00000000..e26cad7c --- /dev/null +++ b/examples/custom_3d_three-d/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + diff --git a/examples/custom_3d_three-d/src/lib.rs b/examples/custom_3d_three-d/src/lib.rs new file mode 100644 index 00000000..ff2e4fd1 --- /dev/null +++ b/examples/custom_3d_three-d/src/lib.rs @@ -0,0 +1,19 @@ +mod main; + +// Entry point for wasm +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen(start)] +pub async fn start() -> Result<(), JsValue> { + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + + let web_options = eframe::WebOptions::default(); + eframe::start_web( + "my", + web_options, + Box::new(|cc| Box::new(main::MyApp::new(cc))), + )?; + Ok(()) +} diff --git a/examples/custom_3d_three-d/src/main.rs b/examples/custom_3d_three-d/src/main.rs index 0ca9eb58..aa0f5a53 100644 --- a/examples/custom_3d_three-d/src/main.rs +++ b/examples/custom_3d_three-d/src/main.rs @@ -1,7 +1,9 @@ +#![allow(dead_code)] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release use eframe::egui; +#[cfg(not(target_arch = "wasm32"))] fn main() { let options = eframe::NativeOptions { initial_window_size: Some(egui::vec2(550.0, 610.0)), @@ -17,12 +19,12 @@ fn main() { ); } -struct MyApp { +pub struct MyApp { angle: f32, } impl MyApp { - fn new(_cc: &eframe::CreationContext<'_>) -> Self { + pub fn new(_cc: &eframe::CreationContext<'_>) -> Self { Self { angle: 0.2 } } } @@ -41,7 +43,28 @@ impl eframe::App for MyApp { egui::ScrollArea::both().show(ui, |ui| { egui::Frame::canvas(ui.style()).show(ui, |ui| { - self.custom_painting(ui); + let (rect, response) = + ui.allocate_exact_size(egui::Vec2::splat(512.0), egui::Sense::drag()); + + self.angle += response.drag_delta().x * 0.01; + + // Clone locals so we can move them into the paint callback: + let angle = self.angle; + + let callback = egui::PaintCallback { + rect, + callback: std::sync::Arc::new(egui_glow::CallbackFn::new( + move |info, painter| { + with_three_d(painter.gl(), |three_d| { + three_d.frame( + FrameInput::new(&three_d.context, &info, painter), + angle, + ); + }); + }, + )), + }; + ui.painter().add(callback); }); ui.label("Drag to rotate!"); }); @@ -49,120 +72,160 @@ impl eframe::App for MyApp { } } -impl MyApp { - fn custom_painting(&mut self, ui: &mut egui::Ui) { - let (rect, response) = - ui.allocate_exact_size(egui::Vec2::splat(512.0), egui::Sense::drag()); - - self.angle += response.drag_delta().x * 0.01; - - // Clone locals so we can move them into the paint callback: - let angle = self.angle; - - let callback = egui::PaintCallback { - rect, - callback: std::sync::Arc::new(egui_glow::CallbackFn::new(move |info, painter| { - with_three_d_context(painter.gl(), |three_d| { - paint_with_three_d(three_d, &info, angle); - }); - })), - }; - ui.painter().add(callback); - } -} - -/// We get a [`glow::Context`] from `eframe`, but we want a [`three_d::Context`]. +/// We get a [`glow::Context`] from `eframe` and we want to construct a [`ThreeDApp`]. /// -/// Sadly we can't just create a [`three_d::Context`] in [`MyApp::new`] and pass it -/// to the [`egui::PaintCallback`] because [`three_d::Context`] isn't `Send+Sync`, which -/// [`egui::PaintCallback`] is. -fn with_three_d_context( - gl: &std::sync::Arc, - f: impl FnOnce(&three_d::Context) -> R, -) -> R { +/// Sadly we can't just create a [`ThreeDApp`] in [`MyApp::new`] and pass it +/// to the [`egui::PaintCallback`] because [`glow::Context`] isn't `Send+Sync` on web, which +/// [`egui::PaintCallback`] needs. If you do not target web, then you can construct the [`ThreeDApp`] in [`MyApp::new`]. +fn with_three_d(gl: &std::sync::Arc, f: impl FnOnce(&mut ThreeDApp) -> R) -> R { use std::cell::RefCell; thread_local! { - pub static THREE_D: RefCell> = RefCell::new(None); - } - - // If you are using the depth buffer you need to do this: - #[allow(unsafe_code)] - unsafe { - use glow::HasContext as _; - gl.enable(glow::DEPTH_TEST); - if !cfg!(target_arch = "wasm32") { - gl.disable(glow::FRAMEBUFFER_SRGB); - } - gl.clear(glow::DEPTH_BUFFER_BIT); + pub static THREE_D: RefCell> = RefCell::new(None); } THREE_D.with(|three_d| { let mut three_d = three_d.borrow_mut(); - let three_d = - three_d.get_or_insert_with(|| three_d::Context::from_gl_context(gl.clone()).unwrap()); + let three_d = three_d.get_or_insert_with(|| ThreeDApp::new(gl.clone())); f(three_d) }) } -fn paint_with_three_d(three_d: &three_d::Context, info: &egui::PaintCallbackInfo, angle: f32) { - // Based on https://github.com/asny/three-d/blob/master/examples/triangle/src/main.rs - use three_d::*; - - // Set where to paint - let viewport = info.viewport_in_pixels(); - let viewport = Viewport { - x: viewport.left_px.round() as _, - y: viewport.from_bottom_px.round() as _, - width: viewport.width_px.round() as _, - height: viewport.height_px.round() as _, - }; - - // Respect the egui clip region (e.g. if we are inside an `egui::ScrollArea`). - let clip_rect = info.clip_rect_in_pixels(); - three_d.set_scissor(ScissorBox { - x: clip_rect.left_px.round() as _, - y: clip_rect.from_bottom_px.round() as _, - width: clip_rect.width_px.round() as _, - height: clip_rect.height_px.round() as _, - }); - - let camera = Camera::new_perspective( - three_d, - viewport, - vec3(0.0, 0.0, 2.0), - vec3(0.0, 0.0, 0.0), - vec3(0.0, 1.0, 0.0), - degrees(45.0), - 0.1, - 10.0, - ) - .unwrap(); - - // Create a CPU-side mesh consisting of a single colored triangle - let positions = vec![ - vec3(0.5, -0.5, 0.0), // bottom right - vec3(-0.5, -0.5, 0.0), // bottom left - vec3(0.0, 0.5, 0.0), // top - ]; - let colors = vec![ - Color::new(255, 0, 0, 255), // bottom right - Color::new(0, 255, 0, 255), // bottom left - Color::new(0, 0, 255, 255), // top - ]; - let cpu_mesh = CpuMesh { - positions: Positions::F32(positions), - colors: Some(colors), - ..Default::default() - }; - - let mut model = Gm::new( - Mesh::new(three_d, &cpu_mesh).unwrap(), - ColorMaterial::default(), - ); - - // Set the current transformation of the triangle - model.set_transformation(Mat4::from_angle_y(radians(angle))); - - // Render the triangle with the color material which uses the per vertex colors defined at construction - model.render(&camera, &[]).unwrap(); +/// +/// Translates from egui input to three-d input +/// +pub struct FrameInput<'a> { + screen: three_d::RenderTarget<'a>, + viewport: three_d::Viewport, + scissor_box: three_d::ScissorBox, +} + +impl FrameInput<'_> { + pub fn new( + context: &three_d::Context, + info: &egui::PaintCallbackInfo, + painter: &egui_glow::Painter, + ) -> Self { + use three_d::*; + + // Disable sRGB textures for three-d + #[cfg(not(target_arch = "wasm32"))] + #[allow(unsafe_code)] + unsafe { + use glow::HasContext as _; + context.disable(glow::FRAMEBUFFER_SRGB); + } + + // Constructs a screen render target to render the final image to + let screen = painter.intermediate_fbo().map_or_else( + || { + RenderTarget::screen( + context, + info.viewport.width() as u32, + info.viewport.height() as u32, + ) + }, + |fbo| { + RenderTarget::from_framebuffer( + context, + info.viewport.width() as u32, + info.viewport.height() as u32, + fbo, + ) + }, + ); + + // Set where to paint + let viewport = info.viewport_in_pixels(); + let viewport = Viewport { + x: viewport.left_px.round() as _, + y: viewport.from_bottom_px.round() as _, + width: viewport.width_px.round() as _, + height: viewport.height_px.round() as _, + }; + + // Respect the egui clip region (e.g. if we are inside an `egui::ScrollArea`). + let clip_rect = info.clip_rect_in_pixels(); + let scissor_box = ScissorBox { + x: clip_rect.left_px.round() as _, + y: clip_rect.from_bottom_px.round() as _, + width: clip_rect.width_px.round() as _, + height: clip_rect.height_px.round() as _, + }; + Self { + screen, + scissor_box, + viewport, + } + } +} + +/// +/// Based on the `three-d` [Triangle example](https://github.com/asny/three-d/blob/master/examples/triangle/src/main.rs). +/// This is where you'll need to customize +/// +use three_d::*; +pub struct ThreeDApp { + context: Context, + camera: Camera, + model: Gm, +} + +impl ThreeDApp { + pub fn new(gl: std::sync::Arc) -> Self { + let context = Context::from_gl_context(gl).unwrap(); + // Create a camera + let camera = Camera::new_perspective( + Viewport::new_at_origo(1, 1), + vec3(0.0, 0.0, 2.0), + vec3(0.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + degrees(45.0), + 0.1, + 10.0, + ); + + // Create a CPU-side mesh consisting of a single colored triangle + let positions = vec![ + vec3(0.5, -0.5, 0.0), // bottom right + vec3(-0.5, -0.5, 0.0), // bottom left + vec3(0.0, 0.5, 0.0), // top + ]; + let colors = vec![ + Color::new(255, 0, 0, 255), // bottom right + Color::new(0, 255, 0, 255), // bottom left + Color::new(0, 0, 255, 255), // top + ]; + let cpu_mesh = CpuMesh { + positions: Positions::F32(positions), + colors: Some(colors), + ..Default::default() + }; + + // Construct a model, with a default color material, thereby transferring the mesh data to the GPU + let model = Gm::new(Mesh::new(&context, &cpu_mesh), ColorMaterial::default()); + Self { + context, + camera, + model, + } + } + + pub fn frame(&mut self, frame_input: FrameInput<'_>, angle: f32) -> Option { + // Ensure the viewport matches the current window viewport which changes if the window is resized + self.camera.set_viewport(frame_input.viewport); + + // Set the current transformation of the triangle + self.model + .set_transformation(Mat4::from_angle_y(radians(angle))); + + // Get the screen render target to be able to render something on the screen + frame_input + .screen + // Clear the color and depth of the screen render target + .clear_partially(frame_input.scissor_box, ClearState::depth(1.0)) + // Render the triangle with the color material which uses the per vertex colors defined at construction + .render_partially(frame_input.scissor_box, &self.camera, &[&self.model], &[]); + + frame_input.screen.into_framebuffer() // Take back the screen fbo, we will continue to use it. + } }