From f25ed801f5c043ad9bcb63b1f7976681c320443c Mon Sep 17 00:00:00 2001 From: Tsuki Date: Wed, 21 Aug 2024 22:00:45 +0800 Subject: [PATCH] pusher --- Cargo.toml | 1 + src/camera.rs | 12 +++ src/graphics/collections/agg_fast_path.rs | 22 +++++- src/graphics/font/mod.rs | 24 +++++- src/graphics/mod.rs | 3 + src/graphics/plane.rs | 7 ++ src/graphics/threed.rs | 2 + src/graphics/transforms/mod.rs | 1 + src/graphics/transforms/plane.rs | 88 ++++++++++++++++++++++ src/graphics/transforms/trackball.rs | 2 +- src/pg/mod.rs | 3 +- src/pg/modules/ppi.rs | 54 ++++++++----- src/shaders/font.rs | 39 +++++----- src/ui/operation.rs | 24 ++++-- src/ui/typ/main_load.rs | 7 +- test.png | Bin 1518 -> 15520 bytes 16 files changed, 235 insertions(+), 54 deletions(-) create mode 100644 src/graphics/plane.rs create mode 100644 src/graphics/transforms/plane.rs diff --git a/Cargo.toml b/Cargo.toml index 96f41eb..dd3a379 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ femtovg = "0.9.2" [features] default = ["sdf_font"] +sdfer_use_f64_instead_of_f32 = [] normal_font = [] sdf_font = [] inparser = [] diff --git a/src/camera.rs b/src/camera.rs index dd768a2..b611238 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -33,14 +33,26 @@ impl Camera { self.upward } + pub fn get_center(&self) -> Vec3 { + self.center + } + pub fn set_upward(&mut self, upward: Vec3) { self.upward = upward; } + pub fn set_center(&mut self, center: Vec3) { + self.center = center; + } + pub fn get_view_matrix(&self) -> Mat4x4 { let l = self.center; look_at(&self.pos, &l, &self.upward) } + + pub fn front(&self) -> Vec3 { + self.center - self.pos + } } impl Default for Camera { diff --git a/src/graphics/collections/agg_fast_path.rs b/src/graphics/collections/agg_fast_path.rs index 942283d..0f8b5d1 100644 --- a/src/graphics/collections/agg_fast_path.rs +++ b/src/graphics/collections/agg_fast_path.rs @@ -137,6 +137,15 @@ impl AttaWithBuffer for AggFastPath { config: &::Config, ) -> Result<(Vec, Option>, i32)> { let points = data.iter().map(|v| &v.points).flatten().collect::>(); + + let mut lens = Vec::with_capacity(data.len()); + let mut sum = 0; + for num in data.iter().map(|v| v.len()) { + lens.push(sum); + sum += num; + } + + let point_lens = data.iter().map(|d| d.len()).collect::>(); let mut vbo = Vec::with_capacity(points.len() * 10); for p in points { vbo.extend_from_slice(&[ @@ -145,8 +154,17 @@ impl AttaWithBuffer for AggFastPath { ]); } let mut ebo = vec![]; - for e in data.iter().map(|v| &v.ebo).flatten() { - ebo.extend_from_slice(e); + for (idx, e) in data.iter().map(|v| &v.ebo).enumerate() { + if idx > 0 { + let len = lens[idx - 1] as u32; + for e in e.iter() { + ebo.extend_from_slice(&[e[0] + len, e[1] + len, e[2] + len]); + } + } else { + for e in e.iter() { + ebo.extend_from_slice(e); + } + } } let len = ebo.len() as i32; diff --git a/src/graphics/font/mod.rs b/src/graphics/font/mod.rs index a0a86a6..ef7fac8 100644 --- a/src/graphics/font/mod.rs +++ b/src/graphics/font/mod.rs @@ -147,6 +147,24 @@ impl<'a> Cache<'a> { self.cache.get(&c).unwrap() } + + fn test(&self) { + let mut data = vec![0; 1024 * 1024]; + unsafe { + self.gl + .bind_texture(glow::TEXTURE_2D, Some(self.tex.native())); + self.gl.get_tex_image( + glow::TEXTURE_2D, + 0, + glow::RED, + glow::UNSIGNED_BYTE, + glow::PixelPackData::Slice(&mut data), + ); + self.gl.bind_texture(glow::TEXTURE_2D, None); + } + let img = image::GrayImage::from_raw(1024, 1024, data).unwrap(); + img.save("test.png").unwrap(); + } } #[derive(Clone)] @@ -220,7 +238,7 @@ impl<'a> Text<'a> { let u_fill = self.program.get_uniform_location(&self.gl, "uFill"); unsafe { - self.gl.uniform_4_f32(conf.as_ref(), 5.0, 0.0, 0.0, 0.0); + self.gl.uniform_4_f32(conf.as_ref(), 5.0, 3.0, 0.0, 0.0); self.gl.uniform_1_i32(u_mode.as_ref(), -1); self.gl.uniform_4_f32(u_border.as_ref(), 0.0, 0.0, 0.0, 0.0); self.gl.uniform_4_f32(u_stroke.as_ref(), 1.0, 1.0, 1.0, 1.0); @@ -319,10 +337,10 @@ impl<'a> AttaWithBuffer for Text<'a> { let vbo = gl.create_buffer().unwrap(); gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo)); gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_f32(0, 4, glow::FLOAT, false, 32, 0); + gl.vertex_attrib_pointer_f32(0, 4, glow::FLOAT, false, 48, 0); gl.enable_vertex_attrib_array(1); - gl.vertex_attrib_pointer_f32(1, 4, glow::FLOAT, false, 32, 16); + gl.vertex_attrib_pointer_f32(1, 4, glow::FLOAT, false, 48, 16); gl.bind_vertex_array(None); gl.bind_buffer(glow::ARRAY_BUFFER, None); diff --git a/src/graphics/mod.rs b/src/graphics/mod.rs index 848141d..9d3f337 100644 --- a/src/graphics/mod.rs +++ b/src/graphics/mod.rs @@ -3,6 +3,7 @@ pub mod colormap; mod colormesh; pub mod font; pub mod hello; +pub mod plane; pub mod ppi; pub mod threed; pub mod tools; @@ -14,6 +15,7 @@ use crate::{ components::Program, errors::*, graphics::font::FontConfig, + pg::layout_type::ViewPort, ui::{operation::Projection, typ::CameraOP}, }; @@ -125,6 +127,7 @@ pub trait AttachWithMouse { state: &Self::State, camera: &mut Camera, projection: &mut Projection, + viewport: &ViewPort, ) -> bool; fn reset(&mut self); diff --git a/src/graphics/plane.rs b/src/graphics/plane.rs new file mode 100644 index 0000000..27b08a8 --- /dev/null +++ b/src/graphics/plane.rs @@ -0,0 +1,7 @@ +pub struct Plane {} + +impl Plane { + pub fn new() -> Self { + Self {} + } +} diff --git a/src/graphics/threed.rs b/src/graphics/threed.rs index 4308786..8995769 100644 --- a/src/graphics/threed.rs +++ b/src/graphics/threed.rs @@ -5,6 +5,7 @@ use super::transforms::{position::Position, trackball::TrackballModel}; use super::{AttaWithProgram, AttachWithMouse}; use crate::camera::{self, Camera}; use crate::errors::*; +use crate::pg::layout_type::ViewPort; use crate::ui::operation::Projection; use glow::HasContext; use nalgebra_glm::{Mat4, Vec3}; @@ -46,6 +47,7 @@ impl AttachWithMouse for Trackball { state: &Self::State, camera: &mut Camera, projection: &mut Projection, + viewport: &ViewPort, ) -> bool { match state { &super::MouseState::Wheel(delta) => { diff --git a/src/graphics/transforms/mod.rs b/src/graphics/transforms/mod.rs index 3f61e1d..6749378 100644 --- a/src/graphics/transforms/mod.rs +++ b/src/graphics/transforms/mod.rs @@ -1,3 +1,4 @@ +pub mod plane; pub mod polar; pub mod position; pub mod trackball; diff --git a/src/graphics/transforms/plane.rs b/src/graphics/transforms/plane.rs new file mode 100644 index 0000000..91d3f2e --- /dev/null +++ b/src/graphics/transforms/plane.rs @@ -0,0 +1,88 @@ +use crate::components::{CodeType, Program, Snippet}; +use crate::errors::Result; +use crate::graphics::{AttaWithProgram, AttachWithMouse, MouseState}; +use crate::pg::layout_type::ViewPort; + +use glow::HasContext; +use nalgebra::{Matrix4, Quaternion, Translation3, Unit, UnitQuaternion, Vector3}; +use nalgebra_glm::vec3; + +#[derive(Debug, Clone)] +pub struct PlaneTrans {} + +impl AttachWithMouse for PlaneTrans { + type State = MouseState; + + fn attach_with_mouse( + &mut self, + state: &Self::State, + camera: &mut crate::camera::Camera, + projection: &mut crate::ui::operation::Projection, + viewport: &ViewPort, + ) -> bool { + let watch_vec = camera.front(); + let viewport_size = viewport.size(); + let if_vertical = true; + + match state { + MouseState::Drag { from, delta } => { + if if_vertical { + // Radian + let fov = projection.fov().to_radians(); + let aspect = projection.aspect(); + + // Z_near + let z_near = projection.z_near(); + + let h = 2.0 * z_near * (fov / 2.0).tan(); + let w = h * aspect; + + let move_width = (delta[0] * w) / viewport_size[0]; + let move_height = (delta[1] * h) / viewport_size[1]; + + let up = camera.get_upward(); + let right = watch_vec.cross(&up).normalize(); + let _move = move_width * right + move_height * up; + + camera.set_position(camera.get_position() + _move); + camera.set_center(camera.get_center() + _move); + } else { + } + } + MouseState::Wheel(delta) => { + projection.set_fov((projection.fov() - delta).clamp(15.0, 120.0)); + } + _ => {} + } + true + } + + fn init_camera(&self) -> crate::camera::Camera { + crate::camera::Camera::new( + vec3(0.0, 20.0, 0.0), + vec3(0.0, 0.0, 1.0), + vec3(0.0, 0.0, 0.0), + ) + } + + fn reset(&mut self) {} +} + +impl AttaWithProgram for PlaneTrans { + fn attach_with_program(&self, gl: &glow::Context, program: &Program) -> Result<()> { + unsafe { + let loc = program.get_uniform_location(gl, "trackball_model"); + + let trackball = nalgebra_glm::rotate_x(&nalgebra_glm::identity(), 90.0f32.to_radians()); + + gl.uniform_matrix_4_f32_slice(loc.as_ref(), false, trackball.as_slice()); + } + Ok(()) + } +} + +impl Default for PlaneTrans { + fn default() -> Self { + Self {} + } +} diff --git a/src/graphics/transforms/trackball.rs b/src/graphics/transforms/trackball.rs index 5480d93..b3b1c29 100644 --- a/src/graphics/transforms/trackball.rs +++ b/src/graphics/transforms/trackball.rs @@ -25,7 +25,7 @@ impl TrackballModel { count: 0, model: Matrix4::identity(), renorm_count: 97, - trackball_size: 100.0, + trackball_size: 20.0, x: 0.0, y: 0.0, theta, diff --git a/src/pg/mod.rs b/src/pg/mod.rs index 177b334..ed96f14 100644 --- a/src/pg/mod.rs +++ b/src/pg/mod.rs @@ -11,6 +11,7 @@ use crate::graphics::collections::agg_fast_path::AggFastPath; use crate::graphics::font::Text; use crate::graphics::ppi::PPI; use crate::graphics::threed::Trackball; +use crate::graphics::transforms::plane::PlaneTrans; use crate::graphics::transforms::viewport; use crate::graphics::{AttaWithProgram, AttachWithMouse}; use crate::ui::operation::Operation; @@ -85,7 +86,7 @@ impl<'gl> Programs<'gl> { pub fn draw_modules( &mut self, modules: &mut ModulePackage<'gl>, - operation: &Operation, + operation: &Operation, viewport: &ViewPort, ) -> Result<()> { match &mut modules.modules { diff --git a/src/pg/modules/ppi.rs b/src/pg/modules/ppi.rs index 2b670ce..2522044 100644 --- a/src/pg/modules/ppi.rs +++ b/src/pg/modules/ppi.rs @@ -4,6 +4,7 @@ use font::{FontConfig, LineStyle, PositionText, TextLine}; use glow::HasContext; use std::rc::Rc; use tracker::track; +use transforms::plane::PlaneTrans; use transforms::viewport; use crate::font_manager::{FontSize, FontStyle}; @@ -65,21 +66,39 @@ impl<'b, 'a: 'b> PPIModule<'b, 'a> { config: &PPIModuleConfig, ) -> Result<()> { let config = config.to_line_config(); - // Create the path - let mut path = Path::new(true); - // Will be changed in the future let outskirt = 10.0; - for seg in 0..500 { - let angle = 2f32 * f32::consts::PI / 500.0 * seg as f32; - let x = (angle.cos() * outskirt) as f32; - let y = (angle.sin() * outskirt) as f32; - path.push([x, y, 0.0]); - } - path.finish(); + let mut paths = vec![]; - let (vbo, ebo, len) = self.line_program.bake(&vec![path], &config)?; + for range in 1..=6 { + let r = outskirt / 5.0 * range as f32; + // Create the path + let mut path = Path::new(true); + // Draw the circle + for seg in 0..200 { + let angle = 2f32 * f32::consts::PI / 200.0 * seg as f32; + let x = (angle.cos() * r) as f32; + let y = (angle.sin() * r) as f32; + path.push([x, y, 0.01]); + } + path.finish(); + paths.push(path); + } + + let a = 2.0 * f32::consts::PI / 6.0; + + for _a in 0..7 { + let mut path = Path::new(false); + let x = (a * _a as f32).cos() * outskirt; + let y = (a * _a as f32).sin() * outskirt; + path.push([0.0, 0.0, 0.01]); + path.push([x, y, 0.01]); + path.finish(); + paths.push(path); + } + + let (vbo, ebo, len) = self.line_program.bake(&paths, &config)?; attach.bind_data(&vbo, ebo.as_ref(), len); Ok(()) } @@ -89,12 +108,10 @@ impl<'b, 'a: 'b> PPIModule<'b, 'a> { match font_style { FontConfig::Textline(line_style, font_style) => { - let new_text = TextLine::new("ABC", Some(font_style), None); + let new_text = TextLine::new("Hello,World", Some(font_style), None); let position_text = PositionText::new(new_text, [0.0, 0.0, 0.0], font::Anchor::BottomCenter); - let text_pg_config = config.to_font_config(); - let (vbo, ebo, len) = self.text_program.bake(&position_text, &text_pg_config)?; attach.bind_data(&vbo, ebo.as_ref(), len); } @@ -107,7 +124,8 @@ impl<'b, 'a: 'b> PPIModule<'b, 'a> { impl<'b, 'a: 'b> Module for PPIModule<'b, 'a> { type Cursor = PPIPackage<'a>; type Data = Data; - type Operation = Trackball; + // type Operation = Trackball; + type Operation = PlaneTrans; fn render<'dt>( &mut self, @@ -223,7 +241,7 @@ impl<'b, 'a: 'b> Module for PPIModule<'b, 'a> { // Bind the data self.bind_ppi_pg(&mut ppi_attach, &data.borrow(), &config); self.bind_line_pg(&mut line_attach, &data.borrow(), &config); - self.bind_tick(&mut tick_attach, &data.borrow(), &config); + // self.bind_tick(&mut tick_attach, &data.borrow(), &config); Ok(PPIPackage::new( config, @@ -333,8 +351,8 @@ impl Default for PPIModuleConfig { Self { ticks: true, line_color: [1.0, 1.0, 1.0, 1.0], - line_width: 2.0, - line_antialias: 2.0, + line_width: 1.5, + line_antialias: 0.5, layer: 0, colors: vec![], color_range: [0.0, 0.0], diff --git a/src/shaders/font.rs b/src/shaders/font.rs index 5e128fb..230f048 100644 --- a/src/shaders/font.rs +++ b/src/shaders/font.rs @@ -18,7 +18,6 @@ impl FontVertex { // texcoord (left top, right bottom) layout(location = 0) in vec4 texcoord; - // Relative position (x, y, width, height) layout(location = 1) in vec4 relative_position; @@ -29,7 +28,6 @@ impl FontVertex { void main() { - gl_Position = vec4(0.0, 0.0, 0.0, 1.0); vs_out.texcoord = texcoord; vs_out.pos = relative_position; } @@ -43,6 +41,7 @@ impl FontGeometry { pub fn new() -> Self { let mut transform = Trackball::new().0; let raw = glsl! { + layout(points) in; layout(triangle_strip, max_vertices = 4) out; @@ -55,6 +54,7 @@ impl FontGeometry { uniform vec2 atlas_shape; out vec2 v_texCoord; + out vec2 UV_coord; in VS_OUT { vec4 texcoord; @@ -76,6 +76,10 @@ impl FontGeometry { vec2 tex_coord_lt = gs_in[0].texcoord.xy / atlas_shape; vec2 tex_coord_rb = gs_in[0].texcoord.zw / atlas_shape; + // uv_coord + vec2 uv_coord_lt = gs_in[0].texcoord.xy; + vec2 uv_coord_rb = gs_in[0].texcoord.zw; + // Char Size vec2 size = vec2(width, hgt); @@ -97,19 +101,23 @@ impl FontGeometry { // Emit vertices gl_Position = vec4(ndc_left_top.x, ndc_right_bottom.y, ndc_base_pos.z / ndc_base_pos.w, 1.0); - v_texCoord = vec2(tex_coord_lt.x, tex_coord_lt.y); + v_texCoord = vec2(tex_coord_lt.x, tex_coord_rb.y); + UV_coord = vec2(uv_coord_lt.x, uv_coord_rb.y); EmitVertex(); gl_Position = vec4(ndc_right_bottom.xy, ndc_base_pos.z / ndc_base_pos.w, 1.0); v_texCoord = tex_coord_rb; + UV_coord = uv_coord_rb; EmitVertex(); gl_Position = vec4(ndc_left_top.xy, ndc_base_pos.z / ndc_base_pos.w, 1.0); v_texCoord = tex_coord_lt; + UV_coord = uv_coord_lt; EmitVertex(); gl_Position = vec4(ndc_right_bottom.x, ndc_left_top.y, ndc_base_pos.z / ndc_base_pos.w, 1.0); v_texCoord = vec2(tex_coord_rb.x, tex_coord_lt.y); + UV_coord = vec2(uv_coord_rb.x, uv_coord_lt.y); EmitVertex(); EndPrimitive(); @@ -128,6 +136,7 @@ impl FontFragment { let raw = glsl! { uniform sampler2D atlas_data; in vec2 v_texCoord; + in vec2 UV_coord; uniform vec4 uClipUV; uniform vec4 uSdfConfig; @@ -161,20 +170,14 @@ impl FontFragment { vec4 fillColor = uFill; vec4 strokeColor = uStroke; - float scale = getUVScale(v_texCoord); + float scale = getUVScale(UV_coord); vec4 texture = getTexture(v_texCoord); float dis = texture.r; - if (dis<0.5) { - discard; - }else { - fragColor = vec4(1.0, 1.0, 1.0, 1.0); - } // float sdfRaw = 0.0; // float mark = 0.0; - // float sdf; // float sdfRadius = uSdfConfig.x; @@ -189,21 +192,15 @@ impl FontFragment { // fillColor = vec4(texture.rgb, fillColor.a); // } - // // if (gl_FragCoord.x < uClipUV.x || gl_FragCoord.y < uClipUV.y || gl_FragCoord.x > uClipUV.z || gl_FragCoord.y > uClipUV.w) { - // // discard; - // // } // // Compute mask based on SDF // float mask = clamp(sdf, 0.0, 1.0); - // // Final color blending logic here + // Final color blending logic here + + // fragColor = vec4(fillColor.rgb + mark, mask + mark); + + fragColor = vec4(dis,dis,dis,dis); - // if(mask < 0.5) { - // fragColor = vec4(0.0, 0.0, 0.0, 0.0); - // } else { - // fragColor = vec4(fillColor.rgb, 1.0); - // } - // fragColor = vec4(fillColor.rgb + mark, fillColor.a * mask + mark); - // fragColor = vec4(1.0, 1.0, 1.0, 1.0); } }; diff --git a/src/ui/operation.rs b/src/ui/operation.rs index 60d219b..3baa245 100644 --- a/src/ui/operation.rs +++ b/src/ui/operation.rs @@ -3,6 +3,7 @@ use crate::{ camera::Camera, components::Program, graphics::{AttaWithProgram, AttachWithMouse}, + pg::layout_type::ViewPort, }; pub struct Operation { @@ -23,11 +24,12 @@ impl Operation { } } - pub fn deal_io(&mut self, io: &super::io::IO) { + pub fn deal_io(&mut self, viewport: &ViewPort, io: &super::io::IO) { self.need_update = self.operation.attach_with_mouse( &T::State::from_context(io), &mut self.camera, &mut self.projection, + viewport, ); } @@ -56,10 +58,10 @@ impl Operation { self.operation.attach_with_program(gl, program); } - pub fn attach_with_mouse(&mut self, camera_op: &T::State) -> bool { - self.operation - .attach_with_mouse(camera_op, &mut self.camera, &mut self.projection) - } + // pub fn attach_with_mouse(&mut self, camera_op: &T::State) -> bool { + // self.operation + // .attach_with_mouse(camera_op, &mut self.camera, &mut self.projection) + // } pub fn is_need_update(&self) -> bool { self.need_update @@ -118,6 +120,18 @@ impl Projection { self.fov } + pub fn z_far(&self) -> f32 { + self.z_far + } + + pub fn z_near(&self) -> f32 { + self.z_near + } + + pub fn aspect(&self) -> f32 { + self.aspect + } + fn as_slice(&self) -> &[f32] { self.projection.as_slice() } diff --git a/src/ui/typ/main_load.rs b/src/ui/typ/main_load.rs index a7e007a..34a49ae 100644 --- a/src/ui/typ/main_load.rs +++ b/src/ui/typ/main_load.rs @@ -3,6 +3,7 @@ use std::thread::panicking; use super::{CameraOP, Layout, LayoutAttach, LayoutMod, Size}; use crate::camera::Camera; use crate::graphics::threed::Trackball; +use crate::graphics::transforms::plane::PlaneTrans; use crate::graphics::{AttaWithProgram, AttachWithMouse, MouseState}; use crate::pg::ModulePackage; use crate::pg::{_ModulePackage, layout_type::ViewPort}; @@ -136,7 +137,7 @@ impl LayoutMod for MainLoad { if ui.is_window_hovered() { let cio = IO::new(ui); - apptyp.operation.deal_io(&cio); + apptyp.operation.deal_io(&apptyp.main_viewport, &cio); } }); }); @@ -146,7 +147,7 @@ impl LayoutMod for MainLoad { let typ = MainLoadAttach { main_viewport: ViewPort::::new(gl, true), packages: Vec::new(), - operation: Operation::new(Trackball::default(), 16.0 / 9.0, 45.0, 0.1, 1000.0), + operation: Operation::new(PlaneTrans::default(), 16.0 / 9.0, 45.0, 0.1, 1000.0), }; typ } @@ -159,7 +160,7 @@ impl MainLoad { } pub struct MainLoadAttach<'gl> { - operation: Operation, + operation: Operation, main_viewport: ViewPort, packages: Vec>, } diff --git a/test.png b/test.png index f42ca9ced573558a7bb6cbf8638c927c25f5b7bc..1612f62c53b67a6ccc43f5998cfc078184d0c87c 100644 GIT binary patch literal 15520 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