summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/assets/fonts/ComicNeue-Regular.ttfbin0 -> 54848 bytes
-rw-r--r--src/assets/shaders/bitmap/frag.glsl (renamed from src/assets/shaders/text/frag.glsl)0
-rw-r--r--src/assets/shaders/bitmap/vert.glsl (renamed from src/assets/shaders/text/vert.glsl)0
-rw-r--r--src/assets/shaders/truetype/frag.glsl23
-rw-r--r--src/assets/shaders/truetype/vert.glsl26
-rw-r--r--src/font.rs137
-rw-r--r--src/lib.rs27
-rw-r--r--src/scene.rs79
-rw-r--r--src/texture.rs14
9 files changed, 291 insertions, 15 deletions
diff --git a/src/assets/fonts/ComicNeue-Regular.ttf b/src/assets/fonts/ComicNeue-Regular.ttf
new file mode 100644
index 0000000..d454f46
--- /dev/null
+++ b/src/assets/fonts/ComicNeue-Regular.ttf
Binary files differ
diff --git a/src/assets/shaders/text/frag.glsl b/src/assets/shaders/bitmap/frag.glsl
index 94d27c9..94d27c9 100644
--- a/src/assets/shaders/text/frag.glsl
+++ b/src/assets/shaders/bitmap/frag.glsl
diff --git a/src/assets/shaders/text/vert.glsl b/src/assets/shaders/bitmap/vert.glsl
index 4005d75..4005d75 100644
--- a/src/assets/shaders/text/vert.glsl
+++ b/src/assets/shaders/bitmap/vert.glsl
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<char, AtlasInfo>,
+}
+
+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<u8> = 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<i32> = 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<Index>,
}
+pub enum ChannelValues {
+ Translation(Vec<glam::Vec3>),
+ Rotation(Vec<glam::Quat>),
+ Scale(Vec<glam::Vec3>),
+}
+
+pub enum Interpolation {
+ Linear,
+ Step,
+ CubicSpline,
+}
+
+pub struct Channel {
+ pub target: Index,
+ pub interpolation: Interpolation,
+ pub keyframes: Vec<f32>,
+ pub values: ChannelValues,
+}
+
+pub struct Animation {
+ pub channels: Vec<Channel>,
+}
+
pub struct Node {
pub children: Vec<Index>,
pub object: Option<Index>,
@@ -49,6 +72,7 @@ pub struct Scene {
pub textures: Vec<texture::Texture>,
pub materials: Vec<Material>,
pub skins: Vec<Skin>,
+ pub animations: HashMap<String, Animation>,
pub nodes: Vec<Node>,
pub nodes_by_name: HashMap<String, Index>,
pub scene_nodes: Vec<Index>,
@@ -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()