From 82d1c94d999654bda5d40108393eade80b77342c Mon Sep 17 00:00:00 2001 From: LLLL Colonq Date: Wed, 15 Jan 2025 21:21:22 -0500 Subject: Update --- src/assets/fonts/ComicNeue-Regular.ttf | Bin 0 -> 54848 bytes src/assets/shaders/bitmap/frag.glsl | 59 ++++++++++++++ src/assets/shaders/bitmap/vert.glsl | 26 +++++++ src/assets/shaders/text/frag.glsl | 59 -------------- src/assets/shaders/text/vert.glsl | 26 ------- src/assets/shaders/truetype/frag.glsl | 23 ++++++ src/assets/shaders/truetype/vert.glsl | 26 +++++++ src/font.rs | 137 ++++++++++++++++++++++++++++++++- src/lib.rs | 27 ++++--- src/scene.rs | 79 +++++++++++++++++++ src/texture.rs | 14 ++++ 11 files changed, 376 insertions(+), 100 deletions(-) create mode 100644 src/assets/fonts/ComicNeue-Regular.ttf create mode 100644 src/assets/shaders/bitmap/frag.glsl create mode 100644 src/assets/shaders/bitmap/vert.glsl delete mode 100644 src/assets/shaders/text/frag.glsl delete mode 100644 src/assets/shaders/text/vert.glsl create mode 100644 src/assets/shaders/truetype/frag.glsl create mode 100644 src/assets/shaders/truetype/vert.glsl (limited to 'src') diff --git a/src/assets/fonts/ComicNeue-Regular.ttf b/src/assets/fonts/ComicNeue-Regular.ttf new file mode 100644 index 0000000..d454f46 Binary files /dev/null and b/src/assets/fonts/ComicNeue-Regular.ttf differ diff --git a/src/assets/shaders/bitmap/frag.glsl b/src/assets/shaders/bitmap/frag.glsl new file mode 100644 index 0000000..94d27c9 --- /dev/null +++ b/src/assets/shaders/bitmap/frag.glsl @@ -0,0 +1,59 @@ +#version 300 es +precision highp float; + +uniform sampler2D texture_data; +uniform int text_length; +uniform int text[256]; +uniform int char_width; +uniform int char_height; +uniform int font_width; +uniform int font_height; +uniform int text_width; +uniform int text_height; +uniform vec3 text_color; + +in vec2 vertex_texcoord; +out vec4 frag_color; + +void main() +{ + vec2 inverted_texcoord = vec2(vertex_texcoord.x, 1.0 - vertex_texcoord.y); + vec2 texcoord_pixels = inverted_texcoord * vec2(float(text_width), float(text_height)); + int texcoord_char_x = int(floor(texcoord_pixels.x)) / char_width; + int texcoord_char_y = int(floor(texcoord_pixels.y)) / char_height; + + int x = 0; + int y = 0; + int i = 0; + for (; i < text_length; ++i) { + if (x == texcoord_char_x && y == texcoord_char_y) { + break; + } + if (text[i] == 10) { + x = 0; + y += 1; + } else { + x += 1; + } + } + if (i == text_length || text[i] == 10) discard; + + int entry = text[i] - 32; + vec2 texcoord_base = vec2( + float(entry % (font_width / char_width)) * float(char_width), + float(entry / (font_width / char_width)) * float(char_height) + ); + // vec2 texcoord_base = vec2(8.0, 0.0); + vec2 texcoord_off = vec2( + mod(texcoord_pixels.x, float(char_width)), + mod(texcoord_pixels.y, float(char_height)) + ); + vec2 texcoord_final = (texcoord_base + texcoord_off) / vec2(float(font_width), float(font_height)); + + vec4 texel = texture(texture_data, texcoord_final); + if (texel.rgb == vec3(0.0, 0.0, 0.0)) discard; + texel.r = text_color.r; + texel.g = text_color.g; + texel.b = text_color.b; + frag_color = texel; +} diff --git a/src/assets/shaders/bitmap/vert.glsl b/src/assets/shaders/bitmap/vert.glsl new file mode 100644 index 0000000..4005d75 --- /dev/null +++ b/src/assets/shaders/bitmap/vert.glsl @@ -0,0 +1,26 @@ +#version 300 es +precision highp float; + +uniform mat4 view; +uniform mat4 position; + +out vec2 vertex_texcoord; + +void main() { + const vec2 positions[4] = vec2[]( + vec2(-1, -1), + vec2(+1, -1), + vec2(-1, +1), + vec2(+1, +1) + ); + const vec2 coords[4] = vec2[]( + vec2(0, 0), + vec2(1, 0), + vec2(0, 1), + vec2(1, 1) + ); + vec4 vertex = vec4(positions[gl_VertexID], 0.0, 1.0); + + vertex_texcoord = coords[gl_VertexID]; + gl_Position = view * position * vertex; +} diff --git a/src/assets/shaders/text/frag.glsl b/src/assets/shaders/text/frag.glsl deleted file mode 100644 index 94d27c9..0000000 --- a/src/assets/shaders/text/frag.glsl +++ /dev/null @@ -1,59 +0,0 @@ -#version 300 es -precision highp float; - -uniform sampler2D texture_data; -uniform int text_length; -uniform int text[256]; -uniform int char_width; -uniform int char_height; -uniform int font_width; -uniform int font_height; -uniform int text_width; -uniform int text_height; -uniform vec3 text_color; - -in vec2 vertex_texcoord; -out vec4 frag_color; - -void main() -{ - vec2 inverted_texcoord = vec2(vertex_texcoord.x, 1.0 - vertex_texcoord.y); - vec2 texcoord_pixels = inverted_texcoord * vec2(float(text_width), float(text_height)); - int texcoord_char_x = int(floor(texcoord_pixels.x)) / char_width; - int texcoord_char_y = int(floor(texcoord_pixels.y)) / char_height; - - int x = 0; - int y = 0; - int i = 0; - for (; i < text_length; ++i) { - if (x == texcoord_char_x && y == texcoord_char_y) { - break; - } - if (text[i] == 10) { - x = 0; - y += 1; - } else { - x += 1; - } - } - if (i == text_length || text[i] == 10) discard; - - int entry = text[i] - 32; - vec2 texcoord_base = vec2( - float(entry % (font_width / char_width)) * float(char_width), - float(entry / (font_width / char_width)) * float(char_height) - ); - // vec2 texcoord_base = vec2(8.0, 0.0); - vec2 texcoord_off = vec2( - mod(texcoord_pixels.x, float(char_width)), - mod(texcoord_pixels.y, float(char_height)) - ); - vec2 texcoord_final = (texcoord_base + texcoord_off) / vec2(float(font_width), float(font_height)); - - vec4 texel = texture(texture_data, texcoord_final); - if (texel.rgb == vec3(0.0, 0.0, 0.0)) discard; - texel.r = text_color.r; - texel.g = text_color.g; - texel.b = text_color.b; - frag_color = texel; -} diff --git a/src/assets/shaders/text/vert.glsl b/src/assets/shaders/text/vert.glsl deleted file mode 100644 index 4005d75..0000000 --- a/src/assets/shaders/text/vert.glsl +++ /dev/null @@ -1,26 +0,0 @@ -#version 300 es -precision highp float; - -uniform mat4 view; -uniform mat4 position; - -out vec2 vertex_texcoord; - -void main() { - const vec2 positions[4] = vec2[]( - vec2(-1, -1), - vec2(+1, -1), - vec2(-1, +1), - vec2(+1, +1) - ); - const vec2 coords[4] = vec2[]( - vec2(0, 0), - vec2(1, 0), - vec2(0, 1), - vec2(1, 1) - ); - vec4 vertex = vec4(positions[gl_VertexID], 0.0, 1.0); - - vertex_texcoord = coords[gl_VertexID]; - gl_Position = view * position * vertex; -} diff --git a/src/assets/shaders/truetype/frag.glsl b/src/assets/shaders/truetype/frag.glsl new file mode 100644 index 0000000..b0e25bf --- /dev/null +++ b/src/assets/shaders/truetype/frag.glsl @@ -0,0 +1,23 @@ +#version 300 es +precision highp float; + +uniform sampler2D texture_data; +uniform int text[256]; +uniform int atlas_width; +uniform int cell_width; +uniform int text_width; + +in vec2 vertex_texcoord; +out vec4 frag_color; + +void main() +{ + vec2 inverted_texcoord = vec2(vertex_texcoord.x, 1.0 - vertex_texcoord.y); + float texcoord_pixels_x = inverted_texcoord.x * float(text_width); + int char_idx = int(floor(texcoord_pixels_x)) / cell_width; + int offset = text[char_idx]; + float cbase = float(offset); + float coff = mod(texcoord_pixels_x, float(cell_width)); + float val = texture(texture_data, vec2((cbase + coff) / float(atlas_width), inverted_texcoord.y)).r; + frag_color = vec4(val, val, val, 1.0); +} diff --git a/src/assets/shaders/truetype/vert.glsl b/src/assets/shaders/truetype/vert.glsl new file mode 100644 index 0000000..4005d75 --- /dev/null +++ b/src/assets/shaders/truetype/vert.glsl @@ -0,0 +1,26 @@ +#version 300 es +precision highp float; + +uniform mat4 view; +uniform mat4 position; + +out vec2 vertex_texcoord; + +void main() { + const vec2 positions[4] = vec2[]( + vec2(-1, -1), + vec2(+1, -1), + vec2(-1, +1), + vec2(+1, +1) + ); + const vec2 coords[4] = vec2[]( + vec2(0, 0), + vec2(1, 0), + vec2(0, 1), + vec2(1, 1) + ); + vec4 vertex = vec4(positions[gl_VertexID], 0.0, 1.0); + + vertex_texcoord = coords[gl_VertexID]; + gl_Position = view * position * vertex; +} diff --git a/src/font.rs b/src/font.rs index 393b1f5..fb8f133 100644 --- a/src/font.rs +++ b/src/font.rs @@ -1,21 +1,24 @@ +use std::collections::HashMap; + use crate::{context, texture, shader}; +use glow::HasContext; pub const CHAR_WIDTH: i32 = 7; pub const CHAR_HEIGHT: i32 = 9; pub const FONT_WIDTH: i32 = 112; pub const FONT_HEIGHT: i32 = 54; -pub struct Font { +pub struct Bitmap { pub shader: shader::Shader, pub font: texture::Texture, } -impl Font { +impl Bitmap { pub fn new(ctx: &context::Context) -> Self { let shader = shader::Shader::new_nolib( &ctx, - include_str!("assets/shaders/text/vert.glsl"), - include_str!("assets/shaders/text/frag.glsl"), + include_str!("assets/shaders/bitmap/vert.glsl"), + include_str!("assets/shaders/bitmap/frag.glsl"), ); let font = texture::Texture::new(ctx, include_bytes!("assets/fonts/simple.png")); Self { @@ -85,3 +88,129 @@ impl Font { self.render_text_helper(ctx, pos, text, &glam::Vec3::new(1.0, 1.0, 1.0)); } } + +pub struct AtlasInfo { + pub pos: usize, +} + +pub struct TrueType { + pub shader: shader::Shader, + pub font: fontdue::Font, + pub atlas: texture::Texture, + pub atlaswidth: usize, + pub cellwidth: usize, + pub cellheight: usize, + pub info: HashMap, +} + +impl TrueType { + pub fn new(ctx: &context::Context) -> Self { + let shader = shader::Shader::new_nolib( + &ctx, + include_str!("assets/shaders/truetype/vert.glsl"), + include_str!("assets/shaders/truetype/frag.glsl"), + ); + let size = 20.0; + let font = fontdue::Font::from_bytes( + include_bytes!("assets/fonts/ComicNeue-Regular.ttf") as &[u8], + fontdue::FontSettings::default(), + ).expect("failed to load font"); + let mut chardata = HashMap::new(); + for ci in 0..128 { + if let Some(c) = char::from_u32(ci) { + if !c.is_ascii_graphic() { continue; } + let res = font.rasterize(c, size); + chardata.insert(c, res); + } + } + let mut cellwidth = 0; + let mut cellbase = 0; + let mut cellextra = 0; + for (_, (m, _)) in &chardata { + if m.width > cellwidth { cellwidth = m.width } + if m.height > cellbase { cellbase = m.height } + let extra = (-m.ymin.min(0)) as usize; + if extra > cellextra { cellextra = extra } + } + let mut cellheight = cellbase + cellextra; + cellwidth = cellwidth.next_power_of_two(); + cellheight = cellheight.next_power_of_two(); + let atlaswidth = (chardata.len() * cellwidth).next_power_of_two(); + let mut info = HashMap::new(); + let mut atlas_bmp: Vec = vec![0; atlaswidth * cellheight]; + for (i, (c, (m, bmp))) in chardata.iter().enumerate() { + let by = ((cellbase as i32) - (m.height as i32) - m.ymin) as usize; + let bx = cellwidth * i; + info.insert(*c, AtlasInfo { + pos: cellwidth * i, + }); + for x in 0..m.width { + for y in 0..m.height { + atlas_bmp[bx + x + (by + y) * atlaswidth] = bmp[x + y * m.width]; + } + } + } + let atlas = texture::Texture::new_empty(ctx); + unsafe { + ctx.gl.bind_texture(glow::TEXTURE_2D, Some(atlas.tex)); + ctx.gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1); + ctx.gl.tex_image_2d( + glow::TEXTURE_2D, + 0, + glow::R8 as i32, + atlaswidth as i32, + cellheight as i32, + 0, + glow::RED, + glow::UNSIGNED_BYTE, + Some(&atlas_bmp), + ); + ctx.gl.generate_mipmap(glow::TEXTURE_2D); + } + Self { shader, font, atlas, atlaswidth, cellwidth, cellheight, info, } + } + + pub fn render_text(&self, ctx: &context::Context, pos: &glam::Vec2, text: &str) { + self.shader.bind(ctx); + unsafe { + ctx.gl.active_texture(glow::TEXTURE0); + ctx.gl.bind_texture(glow::TEXTURE_2D, Some(self.atlas.tex)); + } + self.shader.set_mat4( + ctx, "view", + &glam::Mat4::from_scale( + glam::Vec3::new( + 2.0 / context::RENDER_WIDTH, + 2.0 / context::RENDER_HEIGHT, + 1.0, + ), + ), + ); + let width = text.len() * self.cellwidth; + self.shader.set_mat4( + ctx, "position", + &glam::Mat4::from_scale_rotation_translation( + glam::Vec3::new(width as f32 / 2.0, self.cellheight as f32 / 2.0, 1.0), + glam::Quat::IDENTITY, + glam::Vec3::new( + -context::RENDER_WIDTH / 2.0 + pos.x + width as f32 / 2.0, + context::RENDER_HEIGHT / 2.0 - pos.y - self.cellheight as f32 / 2.0, + 0.0, + ), + ) + ); + let len = text.len().min(256); + let textvals: Vec = text.chars().take(len).map(|c| { + if let Some(i) = self.info.get(&c) { + i.pos as i32 + } else { + 0 + } + }).collect(); + self.shader.set_i32_array(ctx, "text[0]", &textvals); + self.shader.set_i32(ctx, "atlas_width", self.atlaswidth as i32); + self.shader.set_i32(ctx, "cell_width", self.cellwidth as i32); + self.shader.set_i32(ctx, "text_width", width as i32); + ctx.render_no_geometry(); + } +} diff --git a/src/lib.rs b/src/lib.rs index 36ea8bf..f50bb09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,8 @@ pub mod font; pub mod shadow; pub mod audio; pub mod net; +use std::ops::Rem; + #[cfg(target_arch = "wasm32")] use winit::platform::web::EventLoopExtWebSys; @@ -272,7 +274,8 @@ use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] struct TestGame { - font: font::Font, + font: font::Bitmap, + tt: font::TrueType, cube: mesh::Mesh, fox: scene::Scene, tex: texture::Texture, @@ -281,9 +284,9 @@ struct TestGame { impl TestGame { pub async fn new(ctx: &context::Context) -> Self { Self { - font: font::Font::new(ctx), + font: font::Bitmap::new(ctx), + tt: font::TrueType::new(ctx), cube: mesh::Mesh::from_obj(ctx, include_bytes!("assets/meshes/cube.obj")), - // fox: scene::Scene::from_gltf(ctx, include_bytes!("/home/llll/src/colonq/assets/lcolonq_flat.vrm")), fox: scene::Scene::from_gltf(ctx, include_bytes!("assets/scenes/fox.glb")), tex: texture::Texture::new(ctx, include_bytes!("assets/textures/test.png")), shader: shader::Shader::new(ctx, include_str!("assets/shaders/scene/vert.glsl"), include_str!("assets/shaders/scene/frag.glsl")), @@ -294,34 +297,36 @@ impl state::Game for TestGame { fn update(&mut self, ctx: &context::Context, st: &mut state::State) -> Option<()> { st.move_camera( ctx, - &glam::Vec3::new(0.0, 0.0, -10.0), + &glam::Vec3::new(0.0, 0.0, -1.0), &glam::Vec3::new(0.0, 0.0, 1.0), &glam::Vec3::new(0.0, 1.0, 0.0), ); Some(()) } fn render(&mut self, ctx: &context::Context, st: &mut state::State) -> Option<()> { - if let Some(n) = self.fox.nodes_by_name.get("b_Neck_04").and_then(|i| self.fox.nodes.get_mut(*i)) { - n.transform *= glam::Mat4::from_rotation_y(0.05); - } + // if let Some(n) = self.fox.nodes_by_name.get("J_Bip_C_Neck").and_then(|i| self.fox.nodes.get_mut(*i)) { + // n.transform *= glam::Mat4::from_rotation_z(0.05); + // } + self.fox.reflect_animation("Run", (st.tick as f32 / 60.0).rem(3.0)); st.bind_3d(ctx, &self.shader); self.shader.set_position_3d( ctx, &glam::Mat4::from_scale_rotation_translation( - glam::Vec3::new(0.1, 0.1, 0.1), + glam::Vec3::new(0.005, 0.005, 0.005), glam::Quat::from_rotation_y(st.tick as f32 / 60.0), - glam::Vec3::new(0.0, -5.0, 0.0), + glam::Vec3::new(0.0, -0.2, 0.0), ), ); self.tex.bind(ctx); self.fox.render(ctx, &self.shader); - self.font.render_text(ctx, &glam::Vec2::new(0.0, 0.0), "he's all fucked up"); + self.font.render_text(ctx, &glam::Vec2::new(0.0, 0.0), "he's all FIXED up"); + self.tt.render_text(ctx, &glam::Vec2::new(10.0, 10.0), "tESTge"); Some(()) } } #[cfg(target_arch = "wasm32")] #[wasm_bindgen] -pub async fn main_js() { +pub async fn main_js_test() { run(TestGame::new).await; } diff --git a/src/scene.rs b/src/scene.rs index 8c71a12..9e98936 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -37,6 +37,29 @@ pub struct Skin { pub joints: Vec, } +pub enum ChannelValues { + Translation(Vec), + Rotation(Vec), + Scale(Vec), +} + +pub enum Interpolation { + Linear, + Step, + CubicSpline, +} + +pub struct Channel { + pub target: Index, + pub interpolation: Interpolation, + pub keyframes: Vec, + pub values: ChannelValues, +} + +pub struct Animation { + pub channels: Vec, +} + pub struct Node { pub children: Vec, pub object: Option, @@ -49,6 +72,7 @@ pub struct Scene { pub textures: Vec, pub materials: Vec, pub skins: Vec, + pub animations: HashMap, pub nodes: Vec, pub nodes_by_name: HashMap, pub scene_nodes: Vec, @@ -232,6 +256,31 @@ impl Scene { } }).collect(); + let animations = HashMap::from_iter(gltf.animations().filter_map(|a| { + let channels = a.channels().map(|c| { + let read = c.reader(get_buffer_data); + Channel { + target: c.target().node().index(), + interpolation: match c.sampler().interpolation() { + gltf::animation::Interpolation::Linear => Interpolation::Linear, + gltf::animation::Interpolation::Step => Interpolation::Step, + gltf::animation::Interpolation::CubicSpline => Interpolation::CubicSpline, + }, + keyframes: read.read_inputs().expect("channel has no inputs").collect(), + values: match read.read_outputs().expect("channel has no outputs") { + gltf::animation::util::ReadOutputs::Translations(ts) => + ChannelValues::Translation(ts.map(glam::Vec3::from_array).collect()), + gltf::animation::util::ReadOutputs::Rotations(ts) => + ChannelValues::Rotation(ts.into_f32().map(glam::Quat::from_array).collect()), + gltf::animation::util::ReadOutputs::Scales(ts) => + ChannelValues::Scale(ts.map(glam::Vec3::from_array).collect()), + _ => panic!("unsupport channel outputs"), + }, + } + }).collect(); + a.name().map(|nm| (nm.to_owned(), Animation { channels })) + })); + let nodes = gltf.nodes().map(|n| { Node { children: n.children().map(|c| c.index()).collect(), @@ -255,6 +304,7 @@ impl Scene { textures, materials, skins, + animations, nodes, nodes_by_name, scene_nodes, @@ -309,4 +359,33 @@ impl Scene { } } } + + pub fn reflect_animation(&mut self, nm: &str, time: f32) { + if let Some(anim) = self.animations.get(nm) { + for c in &anim.channels { + let (previ, nexti) = if let Some(nexti) = c.keyframes.iter().position(|x| *x > time) { + let previ = if nexti > 0 { nexti - 1 } else { c.keyframes.len() - 1 }; + (previ, nexti) + } else { + (c.keyframes.len() - 1, 0) + }; + match &c.values { + ChannelValues::Rotation(vs) => { + let prevt = c.keyframes[previ]; + let nextt = c.keyframes[nexti]; + let prev = vs[previ]; + let next = vs[nexti]; + let new = prev.slerp(next, (time - prevt) / (nextt - prevt)); + let (scale, _, trans) = self.nodes[c.target].transform.to_scale_rotation_translation(); + self.nodes[c.target].transform = glam::Mat4::from_scale_rotation_translation( + scale, + new, + trans, + ); + }, + _ => {}, + } + } + } + } } diff --git a/src/texture.rs b/src/texture.rs index 2fd9aab..68272cb 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -8,6 +8,20 @@ pub struct Texture { } impl Texture { + pub fn new_empty(ctx: &context::Context) -> Self { + unsafe { + let tex = ctx.gl.create_texture().expect("failed to create texture"); + ctx.gl.bind_texture(glow::TEXTURE_2D, Some(tex)); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::NEAREST as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::NEAREST as i32); + Self { + tex, + } + } + } + pub fn new(ctx: &context::Context, bytes: &[u8]) -> Self { let rgba = image::io::Reader::new(std::io::Cursor::new(bytes)) .with_guessed_format() -- cgit v1.2.3