From 2e695260692c90c6a54f594ff39f213fe073dbaf Mon Sep 17 00:00:00 2001 From: Imbris Date: Mon, 15 Apr 2019 02:07:56 -0400 Subject: [PATCH] initial setup for .vox rendering Former-commit-id: bc3d2432c41d9dc3e1f57e569f3aecb9be3fe79a --- Cargo.lock | 42 +++++ voxygen/Cargo.toml | 3 + voxygen/src/hud/mod.rs | 4 +- voxygen/src/menu/char_selection/ui.rs | 2 +- voxygen/src/menu/main/ui.rs | 2 +- voxygen/src/ui/graphic.rs | 92 ++++++++++ voxygen/src/ui/mod.rs | 157 ++++++++++------- voxygen/src/ui/veuc.rs | 241 ++++++++++++++++++++++++++ 8 files changed, 474 insertions(+), 69 deletions(-) create mode 100644 voxygen/src/ui/graphic.rs create mode 100644 voxygen/src/ui/veuc.rs diff --git a/Cargo.lock b/Cargo.lock index 1a1ab9e19a..a21ae7f43d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -461,6 +461,33 @@ dependencies = [ "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "euc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "vek 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "euclid" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "euclid_macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "euclid_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "failure" version = "0.1.5" @@ -668,6 +695,14 @@ dependencies = [ "x11-dl 2.18.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "guillotiere" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "euclid 0.19.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "hibitset" version = "0.5.4" @@ -1943,12 +1978,15 @@ dependencies = [ "conrod_core 0.63.0 (git+https://gitlab.com/veloren/conrod.git)", "conrod_winit 0.63.0 (git+https://gitlab.com/veloren/conrod.git)", "dot_vox 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "euc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "gfx 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)", "gfx_device_gl 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", "gfx_window_glutin 0.28.0 (registry+https://github.com/rust-lang/crates.io-index)", "glsl-include 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "glutin 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", + "guillotiere 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2215,6 +2253,9 @@ dependencies = [ "checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" "checksum enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" "checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a" +"checksum euc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0151594c4feeeb99ff35ac1b467383a46fcb2705275615bed0a47f25ffe2ccf8" +"checksum euclid 0.19.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7a4719a544a67ed3fc33784c2bd2c6581663dfe83b719a6ae05c6dabc3b51c73" +"checksum euclid_macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fdcb84c18ea5037a1c5a23039b4ff29403abce2e0d6b1daa11cf0bde2b30be15" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" @@ -2238,6 +2279,7 @@ dependencies = [ "checksum gleam 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)" = "39bb69499005e11b7b7cc0af38404a1bc0f53d954bffa8adcdb6e8d5b14f75d5" "checksum glsl-include 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "31c109a006ad24fd612da10d185b51000ef502155578f3634416f102f0d63b6c" "checksum glutin 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "535c6eda58adbb227604b2db10a022ffd6339d7ea3e970f338e7d98aeb24fcc3" +"checksum guillotiere 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "46e965c66630b3a0369feafb06d945f15a4f59aaecc209eb1c4a2b57bb48ee06" "checksum hibitset 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6527bc88f32e0d3926c7572874b2bf17a19b36978aacd0aacf75f7d27a5992d0" "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" "checksum image 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "545f000e8aa4e569e93f49c446987133452e0091c2494ac3efd3606aa3d309f2" diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 4b9c11fa2b..a4f2f5875d 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -22,6 +22,7 @@ glutin = "0.19" winit = {version = "0.18", features = ["serde"]} conrod_core = { git = "https://gitlab.com/veloren/conrod.git" } conrod_winit = { git = "https://gitlab.com/veloren/conrod.git" } +euc = "0.2" # ECS specs = "0.14" @@ -41,3 +42,5 @@ config = "0.9" serde = "1.0" serde_derive = "1.0" toml = "0.4" +guillotiere = "0.4" +fnv = "1.0" diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 589b283f85..87b66ae8a6 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -2,7 +2,7 @@ mod chat; use crate::{ render::Renderer, - ui::{ScaleMode, ToggleButton, Ui}, + ui::{self, ScaleMode, ToggleButton, Ui}, window::{Event as WinEvent, Key, Window}, }; use common::assets; @@ -257,7 +257,7 @@ impl Imgs { .as_slice(), ) .unwrap(); - ui.new_image(renderer, &image).unwrap() + ui.new_graphic(ui::Graphic::Image(image)) }; Imgs { // Bag diff --git a/voxygen/src/menu/char_selection/ui.rs b/voxygen/src/menu/char_selection/ui.rs index 34ebd08c70..1a2be0245e 100644 --- a/voxygen/src/menu/char_selection/ui.rs +++ b/voxygen/src/menu/char_selection/ui.rs @@ -235,7 +235,7 @@ impl Imgs { .as_slice(), ) .unwrap(); - ui.new_image(renderer, &image).unwrap() + ui.new_graphic(ui::Graphic::Image(image)) }; Imgs { v_logo: load("element/v_logo.png"), diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index 1a10e3ad14..41e5b17561 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -74,7 +74,7 @@ impl Imgs { .as_slice(), ) .unwrap(); - ui.new_image(renderer, &image).unwrap() + ui.new_graphic(ui::Graphic::Image(image)) }; Imgs { bg: load("background/bg_main.png"), diff --git a/voxygen/src/ui/graphic.rs b/voxygen/src/ui/graphic.rs new file mode 100644 index 0000000000..8e93f8b168 --- /dev/null +++ b/voxygen/src/ui/graphic.rs @@ -0,0 +1,92 @@ +use common::figure::Segment; +use image::DynamicImage; +use guillotiere::{ + AtlasAllocator, + Allocation, + size2, +}; +use fnv::FnvHashMap; +use vek::*; + +pub enum Graphic { + Image(DynamicImage), + Voxel(Segment), +} +#[derive(PartialEq, Eq, Hash, Copy, Clone)] +pub struct Id(u32); + +type Parameters = (Id, Vec2, Aabr); + +pub struct GraphicCache { + atlas: AtlasAllocator, + graphic_map: FnvHashMap, + rect_map: FnvHashMap>, + next_id: u32, +} +impl GraphicCache { + pub fn new(size: Vec2) -> Self { + Self { + atlas: AtlasAllocator::new(size2(i32::from(size.x), i32::from(size.y))), + graphic_map: FnvHashMap::default(), + rect_map: FnvHashMap::default(), + next_id: 0, + } + } + pub fn new_graphic(&mut self, graphic: Graphic) -> Id { + let id = self.next_id; + self.next_id = id.wrapping_add(1); + + let id = Id(id); + self.graphic_map.insert(id, graphic); + + id + } + pub fn cache_res(&mut self, graphic_id: Id, dims: Vec2, source: Aabr, mut cacher: F) -> Option> where F: FnMut(Aabr, Vec<[u8; 4]>) { + match self.rect_map.get(&(graphic_id, dims, source.map(|e| e.to_bits()))) { //<-------- TODO: Replace this with rounded representation of source + Some(aabr) => Some(*aabr), + None => match self.graphic_map.get(&graphic_id) { + Some(graphic) => { + // Allocate rectangle + let aabr = match self.atlas.allocate(size2(i32::from(dims.x + 2), i32::from(dims.y + 2))) { + Some(Allocation{id, rectangle}) => { + let (min, max) = (rectangle.min, rectangle.max); + Aabr { + min: Vec2::new(min.x as u16 + 1, min.y as u16 + 1), + max: Vec2::new(max.x as u16 - 1, max.y as u16 - 1), + } + } + // Out of room + // TODO: make more room by 1. expanding cache size, 2. removing unused allocations, 3. rearranging rectangles + None => return None, + }; + + // Render image + // TODO: use source + let data = match graphic { + Graphic::Image(ref image) => { + image + .resize_exact(u32::from(aabr.size().w), u32::from(aabr.size().h), image::FilterType::Nearest) + .to_rgba() + .pixels() + .map(|p| p.data) + .collect::>() + } + Graphic::Voxel(segment) => { + super::veuc::draw_vox(&segment, aabr.size().into()) + } + }; + + // Draw to allocated area + cacher(aabr, data); + + // Insert area into map for retrieval + self.rect_map.insert((graphic_id, dims, source.map(|e| e.to_bits())), aabr); + + // Return area + Some(aabr) + } + None => None, + } + } + } +} \ No newline at end of file diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs index 4cd1b89da4..e6015af6c0 100644 --- a/voxygen/src/ui/mod.rs +++ b/voxygen/src/ui/mod.rs @@ -1,7 +1,14 @@ mod widgets; +mod graphic; +mod veuc; pub use widgets::toggle_button::ToggleButton; +pub use graphic::Graphic; +use graphic::{ + GraphicCache, + Id as GraphicId, +}; use image::DynamicImage; use conrod_core::{ Ui as CrUi, @@ -81,9 +88,10 @@ impl Event { } pub struct Cache { - blank_texture: Texture, glyph_cache: GlyphCache<'static>, glyph_cache_tex: Texture, + graphic_cache: graphic::GraphicCache, + graphic_cache_tex: Texture, } // TODO: Should functions be returning UiError instead of Error? @@ -93,23 +101,27 @@ impl Cache { const SCALE_TOLERANCE: f32 = 0.1; const POSITION_TOLERANCE: f32 = 0.1; + let graphic_cache_dims = Vec2::new(w * 4, h * 4); Ok(Self { - blank_texture: renderer.create_texture(&DynamicImage::new_rgba8(1, 1))?, glyph_cache: GlyphCache::builder() .dimensions(w as u32, h as u32) .scale_tolerance(SCALE_TOLERANCE) .position_tolerance(POSITION_TOLERANCE) .build(), glyph_cache_tex: renderer.create_dynamic_texture((w, h).into())?, + graphic_cache: GraphicCache::new(graphic_cache_dims), + graphic_cache_tex: renderer.create_dynamic_texture(graphic_cache_dims)?, }) } - pub fn blank_texture(&self) -> &Texture { &self.blank_texture } pub fn glyph_cache_tex(&self) -> &Texture { &self.glyph_cache_tex } pub fn glyph_cache_mut_and_tex(&mut self) -> (&mut GlyphCache<'static>, &Texture) { (&mut self.glyph_cache, &self.glyph_cache_tex) } + pub fn graphic_cache_tex(&self) -> &Texture { &self.graphic_cache_tex } + pub fn graphic_cache_mut_and_tex(&mut self) -> (&mut GraphicCache, &Texture) { (&mut self.graphic_cache, &self.graphic_cache_tex) } + pub fn new_graphic(&mut self, graphic: Graphic) -> GraphicId { self.graphic_cache.new_graphic(graphic) } } enum DrawKind { - Image(ImgId), + Image, // Text and non-textured geometry Plain, } @@ -121,9 +133,9 @@ enum DrawCommand { Scissor(Aabr), } impl DrawCommand { - fn image(model: Model, img_id: ImgId) -> DrawCommand { + fn image(model: Model) -> DrawCommand { DrawCommand::Draw { - kind: DrawKind::Image(img_id), + kind: DrawKind::Image, model, } } @@ -198,7 +210,7 @@ impl Scale { pub struct Ui { ui: CrUi, - image_map: Map>, + image_map: Map, cache: Cache, // Draw commands for the next render draw_commands: Vec, @@ -230,10 +242,12 @@ impl Ui { self.ui.handle_event(Input::Resize(w, h)); } - pub fn new_image(&mut self, renderer: &mut Renderer, image: &DynamicImage) -> Result { - Ok(self.image_map.insert(renderer.create_texture(image)?)) + pub fn new_graphic(&mut self, graphic: Graphic) -> ImgId { + self.image_map.insert(self.cache.new_graphic(graphic)) } + // TODO: add function that creates a blank graphic + pub fn new_font(&mut self, font: Font) -> FontId { self.ui.fonts.insert(font) } @@ -246,7 +260,7 @@ impl Ui { self.ui.set_widgets() } - // Accepts option so widget can be unfocused + // Accepts Option so widget can be unfocused pub fn focus_widget(&mut self, id: Option) { self.ui.keyboard_capture(match id { Some(id) => id, @@ -259,7 +273,7 @@ impl Ui { self.ui.global_input().current.widget_capturing_keyboard } - // Get whether the a widget besides the window is capturing the mouse + // Get whether a widget besides the window is capturing the mouse pub fn no_widget_capturing_mouse(&self) -> bool { self.ui.global_input().current.widget_capturing_mouse.filter(|id| id != &self.ui.window ).is_none() } @@ -305,7 +319,13 @@ impl Ui { self.draw_commands.clear(); let mut mesh = Mesh::new(); - let mut current_img = None; + // TODO: this could be removed entirely if the draw call just used both textures + enum State { + Image, + Plain, + }; + + let mut current_state = State::Plain; let window_scizzor = default_scissor(renderer); let mut current_scizzor = window_scizzor; @@ -314,9 +334,10 @@ impl Ui { // `Plain` state. macro_rules! switch_to_plain_state { () => { - if let Some(image_id) = current_img.take() { - self.draw_commands.push(DrawCommand::image(renderer.create_model(&mesh).unwrap(), image_id)); + if let State::Image = current_state { + self.draw_commands.push(DrawCommand::image(renderer.create_model(&mesh).unwrap())); mesh.clear(); + current_state = State::Plain; } }; } @@ -324,7 +345,7 @@ impl Ui { let p_scale_factor = self.scale.scale_factor_physical(); while let Some(prim) = primitives.next() { - let Primitive {kind, scizzor, id, rect} = prim; + let Primitive {kind, scizzor, id: _id, rect} = prim; // Check for a change in the scizzor let new_scizzor = { @@ -348,12 +369,12 @@ impl Ui { }; if new_scizzor != current_scizzor { // Finish the current command - match current_img.take() { - None => - self.draw_commands.push(DrawCommand::plain(renderer.create_model(&mesh).unwrap())), - Some(image_id) => - self.draw_commands.push(DrawCommand::image(renderer.create_model(&mesh).unwrap(), image_id)), - } + self.draw_commands.push(match current_state { + State::Plain => + DrawCommand::plain(renderer.create_model(&mesh).unwrap()), + State::Image => + DrawCommand::image(renderer.create_model(&mesh).unwrap()), + }); mesh.clear(); // Update the scizzor and produce a command. @@ -375,52 +396,58 @@ impl Ui { use conrod_core::render::PrimitiveKind; match kind { PrimitiveKind::Image { image_id, color, source_rect } => { - // Switch to the `Image` state for this image if we're not in it already. - let new_image_id = image_id; - match current_img { - // If we're already in the drawing mode for this image, we're done. - Some(image_id) if image_id == new_image_id => (), - // If we were in the `Plain` drawing state, switch to Image drawing state. - None => { - self.draw_commands.push(DrawCommand::plain(renderer.create_model(&mesh).unwrap())); - mesh.clear(); - current_img = Some(new_image_id); - } - // If we were drawing a different image, switch state to draw *this* image. - Some(image_id) => { - self.draw_commands.push(DrawCommand::image(renderer.create_model(&mesh).unwrap(), image_id)); - mesh.clear(); - current_img = Some(new_image_id); - } + if let State::Plain = current_state { + self.draw_commands.push(DrawCommand::plain(renderer.create_model(&mesh).unwrap())); + mesh.clear(); + current_state = State::Image; } let color = srgb_to_linear(color.unwrap_or(conrod_core::color::WHITE).to_fsa()); - // Transform the source rectangle into uv coordinates - let (image_w, image_h) = self.image_map - .get(&image_id) - .expect("Image does not exist in image map") - .get_dimensions() - .map(|e| e as f64) - .into_tuple(); - let (uv_l, uv_r, uv_t, uv_b) = match source_rect { - Some(src_rect) => { - let (l, r, b, t) = src_rect.l_r_b_t(); - ((l / image_w) as f32, - (r / image_w) as f32, - (b / image_h) as f32, - (t / image_h) as f32) + let (graphic_cache, cache_tex) = self.cache.graphic_cache_mut_and_tex(); + + let resolution = Vec2::new( + (rect.w() * p_scale_factor) as u16, + (rect.h() * p_scale_factor) as u16, + ); + // Transform the source rectangle into uv coordinate + // TODO: make sure this is right + let source_aabr = { + let (uv_l, uv_r, uv_b, uv_t) = (0.0, 1.0, 0.0, 1.0);/*match source_rect { + Some(src_rect) => { + let (l, r, b, t) = src_rect.l_r_b_t(); + ((l / image_w) as f32, + (r / image_w) as f32, + (b / image_h) as f32, + (t / image_h) as f32) + } + None => (0.0, 1.0, 0.0, 1.0), + };*/ + Aabr { + min: Vec2::new(uv_l, uv_b), + max: Vec2::new(uv_r, uv_t), } - None => (0.0, 1.0, 0.0, 1.0), }; - let uv = Aabr { - min: Vec2::new(uv_l, uv_b), - max: Vec2::new(uv_r, uv_t), + let graphic_id = self.image_map.get(&image_id).expect("Image does not exist in image map"); + let (cache_w, cache_h) = cache_tex.get_dimensions().map(|e| e as f32).into_tuple(); + + // Cache graphic at particular resolution + let uv_aabr = match graphic_cache.cache_res(*graphic_id, resolution, source_aabr, |aabr, data| { + let offset = aabr.min.into_array(); + let size = aabr.size().into_array(); + renderer.update_texture(cache_tex, offset, size, &data); + }) { + Some(aabr) => Aabr { + min: Vec2::new(aabr.min.x as f32 / cache_w, aabr.max.y as f32 / cache_h), + max: Vec2::new(aabr.max.x as f32 / cache_w, aabr.min.y as f32 / cache_h), + }, + None => continue, }; + mesh.push_quad(create_ui_quad( gl_aabr(rect), - uv, + uv_aabr, color, UiMode::Image, )); @@ -535,12 +562,12 @@ impl Ui { } } // Enter the final command - match current_img { - None => - self.draw_commands.push(DrawCommand::plain(renderer.create_model(&mesh).unwrap())), - Some(image_id) => - self.draw_commands.push(DrawCommand::image(renderer.create_model(&mesh).unwrap(), image_id)), - } + self.draw_commands.push(match current_state { + State::Plain => + DrawCommand::plain(renderer.create_model(&mesh).unwrap()), + State::Image => + DrawCommand::image(renderer.create_model(&mesh).unwrap()), + }); // Handle window resizing if let Some(new_dims) = self.window_resized.take() { @@ -560,8 +587,8 @@ impl Ui { } DrawCommand::Draw { kind, model } => { let tex = match kind { - DrawKind::Image(image_id) => { - self.image_map.get(&image_id).expect("Image does not exist in image map") + DrawKind::Image => { + self.cache.graphic_cache_tex() } DrawKind::Plain => { self.cache.glyph_cache_tex() diff --git a/voxygen/src/ui/veuc.rs b/voxygen/src/ui/veuc.rs new file mode 100644 index 0000000000..65e95f78af --- /dev/null +++ b/voxygen/src/ui/veuc.rs @@ -0,0 +1,241 @@ +use euc::{ + Pipeline, + rasterizer, + buffer::Buffer2d, + Interpolate, +}; +use common::{ + figure::Segment, + vol::{ + Vox, + SizedVol, + ReadVol, + }, +}; +use vek::*; + + +trait Shader { + type VertExtra; + type VsOut: Clone + Interpolate; + fn vert(&self, v_color: Rgba, v_pos: Vec3, vert_extra: &Self::VertExtra) -> (Vec3, Self::VsOut); + fn frag(&self, vs_out: &Self::VsOut) -> Rgba; +} + +struct Voxel where S: Shader { + mvp: Mat4, + shader: S, +} + +struct SimpleShader; +impl Shader for SimpleShader { + type VertExtra = (); + type VsOut = Rgba; + fn vert(&self, v_color: Rgba, v_pos: Vec3, _: &Self::VertExtra) -> (Vec3, Self::VsOut) { + ( + v_pos, + v_color, + ) + } + fn frag(&self, vs_out: &Self::VsOut) -> Rgba { + *vs_out + } +} + +impl<'a, S> Pipeline for Voxel where S: Shader { + type Vertex = (Vec3, Rgb, S::VertExtra); + type VsOut = S::VsOut; + type Pixel = [u8; 4]; + + #[inline(always)] + fn vert(&self, (v_pos, v_color, v_extra): &Self::Vertex) -> ([f32; 3], Self::VsOut) { + let (pos, out) = self.shader.vert( + srgb_to_linear(Rgba::from_opaque(*v_color)), + Vec3::from(self.mvp * Vec4::from_point(*v_pos)), + v_extra, + ); + ( + pos.into_array(), + out, + ) + } + #[inline(always)] + fn frag(&self, vs_out: &Self::VsOut) -> Self::Pixel { + let color = self.shader.frag(vs_out); + linear_to_srgb(color).map(|e| (e * 255.0) as u8).into_array() + } +} + +pub fn draw_vox(segment: &Segment, output_size: Vec2) -> Vec<[u8; 4]> { + let dims = output_size.map(|e| e as usize).into_array(); + let mut color = Buffer2d::new(dims, [50; 4]); + let mut depth = Buffer2d::new(dims, 1.0); + + let mvp = + Mat4::rotation_y(0.6) * + Mat4::::scaling_3d(1.0 / 14.0) * + Mat4::translation_2d([-14.0, -14.0]) * + Mat4::rotation_x(-std::f32::consts::PI / 2.0 ); + Voxel { + mvp, + shader: SimpleShader, + } + .draw::, _>( + &generate_mesh(segment, Vec3::from(0.0)), + &mut color, + &mut depth, + ); + + // TODO: remove this clone + color.as_ref().to_vec() +} + +type Vert = as Pipeline>::Vertex; + +// TODO: generalise meshing code +fn create_quad( + origin: Vec3, + unit_x: Vec3, + unit_y: Vec3, + //norm: Vec3, + col: Rgb, +) -> [Vert; 6] { + let a = (origin, col, ()); + let b = (origin + unit_x, col, ()); + let c = (origin + unit_x + unit_y, col, ()); + let d = (origin + unit_y, col, ()); + [ + a, b, c, // Tri 1 + c, d, a, // Tri 2 + ] +} +fn generate_mesh(segment: &Segment, offs: Vec3) -> Vec { + let mut vertices = Vec::new(); + + for pos in segment.iter_positions() { + if let Some(col) = segment + .get(pos) + .ok() + .and_then(|vox| vox.get_color()) + { + let col = col.map(|e| e as f32 / 255.0); + + // -x + if segment.get(pos - Vec3::unit_x()) + .map(|v| v.is_empty()) + .unwrap_or(true) + { + vertices.extend_from_slice(&create_quad( + offs + pos.map(|e| e as f32) + Vec3::unit_y(), + -Vec3::unit_y(), + Vec3::unit_z(), + //-Vec3::unit_x(), + col, + )); + } + // +x + if segment.get(pos + Vec3::unit_x()) + .map(|v| v.is_empty()) + .unwrap_or(true) + { + vertices.extend_from_slice(&create_quad( + offs + pos.map(|e| e as f32) + Vec3::unit_x(), + Vec3::unit_y(), + Vec3::unit_z(), + //Vec3::unit_x(), + col, + )); + } + // -y + if segment.get(pos - Vec3::unit_y()) + .map(|v| v.is_empty()) + .unwrap_or(true) + { + vertices.extend_from_slice(&create_quad( + offs + pos.map(|e| e as f32), + Vec3::unit_x(), + Vec3::unit_z(), + //-Vec3::unit_y(), + col, + )); + } + // +y + if segment.get(pos + Vec3::unit_y()) + .map(|v| v.is_empty()) + .unwrap_or(true) + { + vertices.extend_from_slice(&create_quad( + offs + pos.map(|e| e as f32) + Vec3::unit_y(), + Vec3::unit_z(), + Vec3::unit_x(), + //Vec3::unit_y(), + col, + )); + } + // -z + if segment.get(pos - Vec3::unit_z()) + .map(|v| v.is_empty()) + .unwrap_or(true) + { + vertices.extend_from_slice(&create_quad( + offs + pos.map(|e| e as f32), + Vec3::unit_y(), + Vec3::unit_x(), + //-Vec3::unit_z(), + col, + )); + } + // +z + if segment.get(pos + Vec3::unit_z()) + .map(|v| v.is_empty()) + .unwrap_or(true) + { + vertices.extend_from_slice(&create_quad( + offs + pos.map(|e| e as f32) + Vec3::unit_z(), + Vec3::unit_x(), + Vec3::unit_y(), + //Vec3::unit_z(), + col, + )); + } + } + } + + vertices +} + +// TODO: put these in utility a module +#[inline(always)] +fn to_linear(x: f32) -> f32 { + if x <= 0.04045 { + x / 12.92 + } else { + ((x + 0.055) / 1.055).powf(2.4) + } +} +#[inline(always)] +fn to_srgb(x: f32) -> f32 { + if x <= 0.0031308 { + x * 12.92 + } else { + x.powf(1.0 / 2.4) * 1.055 - 0.055 + } +} +#[inline(always)] +fn srgb_to_linear(c: Rgba) -> Rgba { + Rgba { + r: to_linear(c.r), + g: to_linear(c.g), + b: to_linear(c.b), + a: c.a, + } +} +#[inline(always)] +fn linear_to_srgb(c: Rgba) -> Rgba { + Rgba { + r: to_srgb(c.r), + g: to_srgb(c.g), + b: to_srgb(c.b), + a: c.a, + } +} \ No newline at end of file